<pre class="metadata">
Title: WebTransport
Shortname: webtransport
Level: none
Status: w3c/ED
Group: webtransport
ED: https://w3c.github.io/webtransport/
TR: https://www.w3.org/TR/webtransport/
Editor: Bernard Aboba, w3cid 65611, Microsoft Corporation
Editor: Nidhi Jaju, w3cid 136840, Google
Editor: Victor Vasiliev, w3cid 113328, Google
Former Editor: Peter Thatcher, Google
Former Editor: Robin Raymond, Optical Tone Ltd.
Former Editor: Yutaka Hirano, Google
Abstract:
  This document defines a set of ECMAScript APIs in WebIDL to allow data to be
  sent and received between a browser and server, utilizing [[!WEB-TRANSPORT-HTTP3]] 
  and [[!WEB-TRANSPORT-HTTP2]]. This specification is being developed in conjunction 
  with protocol specifications developed by the IETF WEBTRANS Working Group.
Repository: w3c/webtransport
Indent: 2
Markup Shorthands: markdown yes
Boilerplate: omit conformance
</pre>
<pre class="biblio">
{
  "quic": {
    "authors": ["Jana Iyengar", "Martin Thomson"],
    "href": "https://www.rfc-editor.org/rfc/rfc9000",
    "title": "QUIC: A UDP-Based Multiplexed and Secure Transport",
    "status": "Proposed Standard",
    "publisher": "IETF"
  },
  "quic-datagram": {
    "authors": ["Tommy Pauly", "Eric Kinnear", "David Schinazi"],
    "href": "https://www.rfc-editor.org/rfc/rfc9221",
    "title": "An Unreliable Datagram Extension to QUIC",
    "status": "Proposed Standard",
    "publisher": "IETF"
  },
  "web-transport-overview": {
    "authors": ["Victor Vasiliev"],
    "href": "https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview",
    "title": "WebTransport Protocol Framework",
    "status": "Internet-Draft",
    "publisher": "IETF"
  },
  "web-transport-http3": {
    "authors": ["Alan Frindell", "Eric Kinnear", "Victor Vasiliev"],
    "href": "https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/",
    "title": "WebTransport over HTTP/3",
    "status": "Internet-Draft",
    "publisher": "IETF"
  },
  "web-transport-http2": {
    "authors": ["Alan Frindell", "Eric Kinnear", "Tommy Pauly", "Martin Thomson", "Victor Vasiliev", "Guowu Xie"],
    "href": "https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http2/",
    "title": "WebTransport over HTTP/2",
    "status": "Internet-Draft",
    "publisher": "IETF"
  }
}
</pre>
<pre class="link-defaults">
spec:infra; type:dfn; for:set; text:for each
spec:infra; type:dfn; for:/; text:set
spec:streams; type:interface; text:ReadableStream
spec:fetch; type:dfn; for:/; text:fetch
spec:fetch; type:dfn; for:/; text:credentials
spec:url; type:dfn; text:scheme
spec:url; type:dfn; text:fragment
spec:infra; type:dfn; for:/; text:ASCII case-insensitive
spec:infra; type:dfn; text:list
</pre>
<pre class="anchors">
url: https://html.spec.whatwg.org/multipage/origin.html#concept-origin; type: dfn; text: origin; for:/
urlPrefix: http://www.ecma-international.org/ecma-262/6.0/index.html; spec: ECMASCRIPT-6.0
  type: dfn
    text: fulfilled; url: sec-promise-objects
    text: rejected; url: sec-promise-objects
    text: pending; url: sec-promise-objects
    text: resolved; url: sec-promise-objects
    text: settled; url: sec-promise-objects
    text: the typed array constructors table; url: #table-49
urlPrefix: https://heycam.github.io/webidl/; spec: WEBIDL
  type: dfn
    text: created; for:DOMException; url: dfn-create-exception
    text: name; for:DOMException; url: domexception-name
    text: message; for:DOMException; url: domexception-message
</pre>

# Introduction #    {#introduction}

*This section is non-normative.*

This specification uses [[!WEB-TRANSPORT-HTTP3]] and [[!WEB-TRANSPORT-HTTP2]] to
send data to and receive data from servers. It can be used like WebSockets but
with support for multiple streams, unidirectional streams, out-of-order delivery,
and reliable as well as unreliable transport.

Note: The API presented in this specification represents a preliminary proposal
based on work-in-progress within the IETF WEBTRANS WG. Since the [[!WEB-TRANSPORT-HTTP3]]
and [[!WEB-TRANSPORT-HTTP2]] specifications are a work-in-progress, both the protocol
and API are likely to change significantly going forward.

# Conformance #  {#conformance}

As well as sections marked as non-normative, all authoring guidelines,
diagrams, examples, and notes in this specification are non-normative.
Everything else in this specification is normative.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
 "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in 
[[!RFC2119]] and [[!RFC8174]] when, and only when, they appear in all capitals, as shown here.

This specification defines conformance criteria that apply to a single product:
the user agent that implements the interfaces that it contains.

Conformance requirements phrased as algorithms or specific steps may be
implemented in any manner, so long as the end result is equivalent. (In
particular, the algorithms defined in this specification are intended to be
easy to follow, and not intended to be performant.)

Implementations that use ECMAScript to implement the APIs defined in this
specification MUST implement them in a manner consistent with the ECMAScript
Bindings defined in the Web IDL specification [[!WEBIDL]], as this
specification uses that specification and terminology.

# Protocol concepts # {#protocol-concepts}

There are two main protocol concepts for WebTransport: sessions and streams. Each [=WebTransport
session=] can contain multiple [=WebTransport streams=].

## WebTransport session ## {#webtransport-session}

A <dfn for="protocol">WebTransport session</dfn> is a session of WebTransport over an HTTP/3
or HTTP/2 <dfn>underlying [=connection=]</dfn>.
There may be multiple [=WebTransport sessions=] on one [=connection=], when pooling is enabled.

A [=WebTransport session=] has the following capabilities defined in [[!WEB-TRANSPORT-OVERVIEW]]:

<table class="data" dfn-for="session">
 <thead>
  <tr>
   <th>capability
   <th>definition
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>send a [datagram](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#name-datagrams)</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.2](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.2-6.2.1)
  </tr>
  <tr>
   <td><dfn>receive a [datagram](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#name-datagrams)</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.2](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.2-6.4.1)
  </tr>
  <tr>
   <td><dfn>create an [=stream/outgoing unidirectional=] stream</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-7.2.1)
  </tr>
  <tr>
   <td><dfn>create a [=stream/bidirectional=] stream</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-7.4.1)
  </tr>
  <tr>
   <td><dfn>receive an [=stream/incoming unidirectional=] stream</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-7.6.1)
  </tr>
  <tr>
   <td><dfn>receive a [=stream/bidirectional=] stream</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-7.8.1)
  </tr>
 </tbody>
</table>

To <dfn for=session>establish</dfn> a [=WebTransport session=] with an [=/origin=] |origin|,
follow [[!WEB-TRANSPORT-OVERVIEW]]
[Section 4.1](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.1-2.2.1),
with using |origin|, [=ASCII serialization of an origin|serialized=] and [=isomorphic encoded=],
as the [:Origin:] header of the request.
When establishing a session, the client MUST NOT provide any [=credentials=].
The resulting underlying transport stream is referred to as the session's <dfn>CONNECT stream</dfn>.

A [=WebTransport session=] |session| is <dfn for=session>draining</dfn> when the
[=CONNECT stream=] receives an [=session-signal/DRAIN_WEBTRANSPORT_SESSION=] capsule, or when a
[=session-signal/GOAWAY=] frame is received, as described in [[!WEB-TRANSPORT-HTTP3]]
[Section 4.6](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.6).

To <dfn for=session>terminate</dfn> a [=WebTransport session=] |session| with an optional integer
|code| and an optional [=byte sequence=] |reason|, follow [[!WEB-TRANSPORT-OVERVIEW]]
[Section 4.1](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.1-2.2.1).

A [=WebTransport session=] |session| is <dfn for=session>terminated</dfn>, with optionally
an integer |code| and a [=byte sequence=] |reason|, when the [=CONNECT stream=] is closed by the server,
as described at [[!WEB-TRANSPORT-OVERVIEW]]
[Section 4.1](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.1-4.2.1).

A [=WebTransport session=] has the following signals:

<table class="data" dfn-for="session-signal">
 <thead>
  <tr>
   <th>event
   <th>definition
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>DRAIN_WEBTRANSPORT_SESSION</dfn>
   <td>[[!WEB-TRANSPORT-HTTP3]]
   [Section 4.6](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.6)
  </tr>
  <tr>
   <td><dfn>GOAWAY</dfn>
   <td>[[!WEB-TRANSPORT-HTTP3]]
   [Section 4.6](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.6)
  </tr>
 </tbody>
</table>

## WebTransport stream ## {#webtransport-stream}

A <dfn for="protocol">WebTransport stream</dfn> is a concept for a reliable in-order stream of
bytes on a [=WebTransport session=], as described in [[!WEB-TRANSPORT-OVERVIEW]]
[Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3).

A [=WebTransport stream=] is one of <dfn for=stream>incoming unidirectional</dfn>,
<dfn for=stream>outgoing unidirectional</dfn> or <dfn for=stream>bidirectional</dfn>.

A [=WebTransport stream=] has the following capabilities:

<table class="data" dfn-for="stream">
 <thead>
  <tr>
   <th>capability
   <th>definition
   <th>[=stream/incoming unidirectional=]
   <th>[=stream/outgoing unidirectional=]
   <th>[=stream/bidirectional=]
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>send</dfn> bytes (potentially with FIN)
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-9.2.1)
   <td>No
   <td>Yes
   <td>Yes
  </tr>
  <tr>
   <td><dfn>receive</dfn> bytes (potentially with FIN)
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-9.4.1)
   <td>Yes
   <td>No
   <td>Yes
  </tr>
  <tr>
   <td><dfn>send STOP_SENDING</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-9.8.1)
   <td>Yes
   <td>No
   <td>Yes
  </tr>
  <tr>
   <td><dfn>reset</dfn> a [=WebTransport stream=]
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-9.6.1)
   <td>No
   <td>Yes
   <td>Yes
  </tr>
 </tbody>
</table>

A [=WebTransport stream=] has the following signals:

<table class="data" dfn-for="stream-signal">
 <thead>
  <tr>
   <th>event
   <th>definition
   <th>[=stream/incoming unidirectional=]
   <th>[=stream/outgoing unidirectional=]
   <th>[=stream/bidirectional=]
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>STOP_SENDING</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-9.8.1)
   <td>No
   <td>Yes
   <td>Yes
  </tr>
  <tr>
   <td><dfn>RESET_STREAM</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-9.6.1)
   <td>Yes
   <td>No
   <td>Yes
  </tr>
  <tr>
   <td><dfn>flow control</dfn>
   <td>[[!WEB-TRANSPORT-OVERVIEW]]
   [Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-06#section-4.3-5)
   <td>No
   <td>Yes
   <td>Yes
  </tr>
 </tbody>
</table>

# `WebTransportDatagramDuplexStream` Interface #  {#duplex-stream}

A <dfn interface>WebTransportDatagramDuplexStream</dfn> is a generic duplex stream.

<pre class="idl">
[Exposed=(Window,Worker), SecureContext]
interface WebTransportDatagramDuplexStream {
  readonly attribute ReadableStream readable;
  readonly attribute WritableStream writable;

  readonly attribute unsigned long maxDatagramSize;
  attribute unrestricted double? incomingMaxAge;
  attribute unrestricted double? outgoingMaxAge;
  attribute unrestricted double incomingHighWaterMark;
  attribute unrestricted double outgoingHighWaterMark;
};
</pre>

## Internal slots ## {#datagramduplexstream-internal-slots}

A {{WebTransportDatagramDuplexStream}} object has the following internal slots.

<table class="data" dfn-for="WebTransportDatagramDuplexStream" dfn-type="attribute">
 <thead>
  <tr>
   <th>Internal Slot
   <th>Description (<em>non-normative</em>)
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>`[[Readable]]`</dfn>
   <td class="non-normative">A {{ReadableStream}} for incoming datagrams.
  </tr>
  <tr>
   <td><dfn>`[[Writable]]`</dfn>
   <td class="non-normative">A {{WritableStream}} for outgoing datagrams.
  </tr>
  <tr>
   <td><dfn>`[[IncomingDatagramsQueue]]`</dfn>
   <td class="non-normative">A queue of pairs of an incoming datagram and a timestamp.
  </tr>
  <tr>
   <td><dfn>`[[IncomingDatagramsPullPromise]]`</dfn>
   <td class="non-normative">A promise set by [=pullDatagrams=], to wait for an incoming datagram.
  </tr>
  <tr>
   <td><dfn>`[[IncomingDatagramsHighWaterMark]]`</dfn>
   <td class="non-normative">An {{unrestricted double}} representing the
   [=high water mark=] of the incoming datagrams.
  </tr>
  <tr>
   <td><dfn>`[[IncomingDatagramsExpirationDuration]]`</dfn>
   <td class="non-normative">An {{unrestricted double}} representing the
   expiration duration for incoming datagrams (in milliseconds), or null.
  </tr>
  <tr>
   <td><dfn>`[[OutgoingDatagramsQueue]]`</dfn>
   <td class="non-normative">A queue of tuples of an outgoing datagram, a timestamp and a promise
   which is resolved when the datagram is sent or discarded.
  </tr>
  <tr>
   <td><dfn>`[[OutgoingDatagramsHighWaterMark]]`</dfn>
   <td class="non-normative">An {{unrestricted double}} representing the
   [=high water mark=] of the outgoing datagrams.
  </tr>
  <tr>
   <td><dfn>`[[OutgoingDatagramsExpirationDuration]]`</dfn>
   <td class="non-normative">An {{unrestricted double}} value representing the
   expiration duration for outgoing datagrams (in milliseconds), or null.
  </tr>
  <tr>
   <td><dfn>`[[OutgoingMaxDatagramSize]]`</dfn>
   <td class="non-normative">An integer representing the maximum size for an outgoing datagram.
  </tr>
 </tbody>
</table>

The user agent MAY update {{[[OutgoingMaxDatagramSize]]}} for any {{WebTransport}} object whose
{{[[State]]}} is either `"connecting"` or `"connected"`.

 To <dfn export for="WebTransportDatagramDuplexStream" lt="create|creating">create</dfn> a
 {{WebTransportDatagramDuplexStream}} given a
 <dfn export for="WebTransportDatagramDuplexStream/create"><var>readable</var></dfn>, and
 a <dfn export for="WebTransportDatagramDuplexStream/create"><var>writable</var></dfn>,
 perform the following steps.

 1. Let |stream| be a [=new=] {{WebTransportDatagramDuplexStream}}, with:
    : {{WebTransportDatagramDuplexStream/[[Readable]]}}
    :: |readable|
    : {{WebTransportDatagramDuplexStream/[[Writable]]}}
    :: |writable|
    : {{[[IncomingDatagramsQueue]]}}
    :: an empty queue
    : {{[[IncomingDatagramsPullPromise]]}}
    :: null
    : {{[[IncomingDatagramsHighWaterMark]]}}
    :: an [=implementation-defined=] value
    : {{[[IncomingDatagramsExpirationDuration]]}}
    :: null
    : {{[[OutgoingDatagramsQueue]]}}
    :: an empty queue
    : {{[[OutgoingDatagramsHighWaterMark]]}}
    :: an [=implementation-defined=] value
       <div class="note">
        <p>This implementation-defined value should be tuned to ensure decent throughput, without
           jeopardizing the timeliness of transmitted data.</p>
       </div>
    : {{[[OutgoingDatagramsExpirationDuration]]}}
    :: null
    : {{[[OutgoingMaxDatagramSize]]}}
    :: an [=implementation-defined=] integer.
 1. Return |stream|.

## Attributes ##  {#datagram-duplex-stream-attributes}

: <dfn for="WebTransportDatagramDuplexStream" attribute>readable</dfn>
:: The getter steps are:
     1. Return [=this=].{{WebTransportDatagramDuplexStream/[[Readable]]}}.

: <dfn for="WebTransportDatagramDuplexStream" attribute>writable</dfn>
:: The getter steps are:
     1. Return [=this=].{{WebTransportDatagramDuplexStream/[[Writable]]}}.

: <dfn for="WebTransportDatagramDuplexStream" attribute>incomingMaxAge</dfn>
:: The getter steps are:
     1. Return [=this=].{{[[IncomingDatagramsExpirationDuration]]}}.
:: The setter steps, given |value|, are:
     1. If |value| is negative or NaN, [=throw=] a {{RangeError}}.
     1. If |value| is `0`, set |value| to null.
     1. Set [=this=].{{[[IncomingDatagramsExpirationDuration]]}} to |value|.

: <dfn for="WebTransportDatagramDuplexStream" attribute>maxDatagramSize</dfn>
:: The maximum size data that may be passed to {{WebTransportDatagramDuplexStream/writable}}.
   The getter steps are to return [=this=].{{[[OutgoingMaxDatagramSize]]}}.

: <dfn for="WebTransportDatagramDuplexStream" attribute>outgoingMaxAge</dfn>
:: The getter steps are:
     1. Return [=this=]'s {{[[OutgoingDatagramsExpirationDuration]]}}.
:: The setter steps, given |value|, are:
     1. If |value| is negative or NaN, [=throw=] a {{RangeError}}.
     1. If |value| is `0`, set |value| to null.
     1. Set [=this=].{{[[OutgoingDatagramsExpirationDuration]]}} to |value|.

: <dfn for="WebTransportDatagramDuplexStream" attribute>incomingHighWaterMark</dfn>
:: The getter steps are:
     1. Return [=this=].{{[[IncomingDatagramsHighWaterMark]]}}.
:: The setter steps, given |value|, are:
     1. If |value| is negative or NaN, [=throw=] a {{RangeError}}.
     1. If |value| is < `1`, set |value| to `1`.
     1. Set [=this=].{{[[IncomingDatagramsHighWaterMark]]}} to |value|.

: <dfn for="WebTransportDatagramDuplexStream" attribute>outgoingHighWaterMark</dfn>
:: The getter steps are:
     1. Return [=this=].{{[[OutgoingDatagramsHighWaterMark]]}}.
:: The setter steps, given |value|, are:
     1. If |value| is negative or NaN, [=throw=] a {{RangeError}}.
     1. If |value| is < `1`, set |value| to `1`.
     1. Set [=this=].{{[[OutgoingDatagramsHighWaterMark]]}} to |value|.

## Procedures ## {#datagram-duplex-stream-procedures}

To <dfn>pullDatagrams</dfn>, given a {{WebTransport}} object |transport|, run these steps:
1. Let |datagrams| be |transport|.{{[[Datagrams]]}}.
1. Assert: |datagrams|.{{[[IncomingDatagramsPullPromise]]}} is null.
1. Let |queue| be |datagrams|.{{[[IncomingDatagramsQueue]]}}.
1. If |queue| is empty, then:
  1. Set |datagrams|.{{[[IncomingDatagramsPullPromise]]}} to a new promise.
  1. Return |datagrams|.{{[[IncomingDatagramsPullPromise]]}}.
1. Let |datagram| and |timestamp| be the result of [=dequeuing=] |queue|.
1. If |datagrams|.{{WebTransportDatagramDuplexStream/[[Readable]]}}'s
   [=ReadableStream/current BYOB request view=] is not null, then:
  1. Let |view| be |datagrams|.{{WebTransportDatagramDuplexStream/[[Readable]]}}'s
     [=ReadableStream/current BYOB request view=].
  1. If |view|'s [=BufferSource/byte length=] is less than the size of |datagram|, return
     [=a promise rejected with=] a {{RangeError}}.
  1. Let |elementSize| be the element size specified in [=the typed array constructors table=] for
     |view|.\[[TypedArrayName]]. If |view| does not have a \[[TypedArrayName]] internal slot
     (i.e. it is a {{DataView}}), let |elementSize| be 0.
  1. If |elementSize| is not 1, return [=a promise rejected with=] a {{TypeError}}.
1. [=ReadableStream/Pull from bytes=] |datagram| into |datagrams|.{{WebTransportDatagramDuplexStream/[[Readable]]}}.
1. Return [=a promise resolved with=] undefined.

To <dfn>receiveDatagrams</dfn>, given a {{WebTransport}} object |transport|, run these steps:
1. Let |timestamp| be a timestamp representing now.
1. Let |queue| be |datagrams|.{{[[IncomingDatagramsQueue]]}}.
1. Let |duration| be |datagrams|.{{[[IncomingDatagramsExpirationDuration]]}}.
1. If |duration| is null, then set |duration| to an [=implementation-defined=] value.
1. Let |session| be |transport|.{{[[Session]]}}.
1. While there are [=session/receive a datagram|available incoming datagrams=] on |session|:
  1. Let |datagram| be the result of [=session/receiving a datagram=] with |session|.
  1. Let |timestamp| be a timestamp representing now.
  1. Let |chunk| be a pair of |datagram| and |timestamp|.
  1. [=queue/Enqueue=] |chunk| to |queue|.
1. Let |toBeRemoved| be the length of |queue| minus |datagrams|.{{[[IncomingDatagramsHighWaterMark]]}}.
1. If |toBeRemoved| is positive, repeat [=queue/dequeuing=] |queue| |toBeRemoved|
   ([rounded down](https://tc39.github.io/ecma262/#eqn-floor)) times.
1. While |queue| is not empty:
  1. Let |bytes| and |timestamp| be |queue|'s first element.
  1. If more than |duration| milliseconds have passed since |timestamp|, then [=queue/dequeue=] |queue|.
  1. Otherwise, [=iteration/break=] this loop.
1. If |queue| is not empty and |datagrams|.{{[[IncomingDatagramsPullPromise]]}} is non-null, then:
  1. Let |bytes| and |timestamp| be the result of [=queue/dequeuing=] |queue|.
  1. Let |promise| be |datagrams|.{{[[IncomingDatagramsPullPromise]]}}.
  1. Set |datagrams|.{{[[IncomingDatagramsPullPromise]]}} to null.
  1. [=WebTransport/Queue a network task=] with |transport| to run the following steps:
    1. Let |chunk| be a new {{Uint8Array}} object representing |bytes|.
    1. [=ReadableStream/Enqueue=] |chunk| to |datagrams|.{{WebTransportDatagramDuplexStream/[[Readable]]}}.
    1. [=Resolve=] |promise| with undefined.

The user agent SHOULD run [=receiveDatagrams=] for any {{WebTransport}} object whose
{{[[State]]}} is `"connected"` as soon as reasonably possible whenever the algorithm can make
progress.

<div algorithm>

The <dfn>writeDatagrams</dfn> algorithm is given a |transport| as parameter and
|data| as input. It is defined by running the following steps:

1. Let |timestamp| be a timestamp representing now.
1. If |data| is not a {{BufferSource}} object, then return [=a promise rejected with=] a {{TypeError}}.
1. Let |datagrams| be |transport|.{{[[Datagrams]]}}.
1. If |datagrams|.{{[[OutgoingMaxDatagramSize]]}} is less than |data|'s \[[ByteLength]], return
   [=a promise resolved with=] undefined.
1. Let |promise| be a new promise.
1. Let |bytes| be a copy of bytes which |data| represents.
1. Let |chunk| be a tuple of |bytes|, |timestamp| and |promise|.
1. Enqueue |chunk| to |datagrams|.{{[[OutgoingDatagramsQueue]]}}.
1. If the length of |datagrams|.{{[[OutgoingDatagramsQueue]]}} is less than
   |datagrams|.{{[[OutgoingDatagramsHighWaterMark]]}}, then [=resolve=] |promise| with undefined.
1. Return |promise|.

Note: The associated {{WritableStream}} calls [=writeDatagrams=] only when all the promises that
have been returned by [=writeDatagrams=] have been resolved. Hence the timestamp and the expiration
duration work well only when the web developer pays attention to
{{WritableStreamDefaultWriter/ready|WritableStreamDefaultWriter.ready}}.

</div>

<div algorithm>

To <dfn>sendDatagrams</dfn>, given a {{WebTransport}} object |transport|, run these steps:
1. Let |queue| be |datagrams|.{{[[OutgoingDatagramsQueue]]}}.
1. Let |duration| be |datagrams|.{{[[OutgoingDatagramsExpirationDuration]]}}.
1. If |duration| is null, then set |duration| to an [=implementation-defined=] value.
1. While |queue| is not empty:
  1. Let |bytes|, |timestamp| and |promise| be |queue|'s first element.
  1. If more than |duration| milliseconds have passed since |timestamp|, then:
     1. Remove the first element from |queue|.
     1. [=Queue a network task=] with |transport| to [=resolve=] |promise| with |undefined|.
  1. Otherwise, break this loop.
1. If |transport|.{{[[State]]}} is not `"connected"`, then return.
1. Let |maxSize| be |datagrams|.{{[[OutgoingMaxDatagramSize]]}}.
1. While |queue| is not empty:
  1. Let |bytes|, |timestamp| and |promise| be |queue|'s first element.
  1. If |bytes|'s length ≤ |maxSize|:
    1. If it is not possible to send |bytes| to the network immediately, then break this loop.
    1. [=session/Send a datagram=], with |transport|.{{[[Session]]}} and |bytes|.
  1. Remove the first element from |queue|.
  1. [=Queue a network task=] with |transport| to [=resolve=] |promise| with undefined.

</div>

The user agent SHOULD run [=sendDatagrams=] for any {{WebTransport}} object whose
{{[[State]]}} is `"connecting"` or `"connected"` as soon as reasonably possible whenever the
algorithm can make progress.

Note: Writing datagrams while the transport's {{[[State]]}} is `"connecting"` is allowed. The
datagrams are stored in {{[[OutgoingDatagramsQueue]]}}, and they can be discarded
in the same manner as when in the `"connected"` state. Once the transport's {{[[State]]}} becomes
`"connected"`, it will start sending the queued datagrams.

# `WebTransport` Interface #  {#web-transport}

`WebTransport` provides an API to the underlying transport functionality
defined in [[!WEB-TRANSPORT-OVERVIEW]].

<pre class="idl">
[Exposed=(Window,Worker), SecureContext]
interface WebTransport {
  constructor(USVString url, optional WebTransportOptions options = {});

  Promise&lt;WebTransportConnectionStats&gt; getStats();
  readonly attribute Promise&lt;undefined&gt; ready;
  readonly attribute WebTransportReliabilityMode reliability;
  readonly attribute WebTransportCongestionControl congestionControl;
  [EnforceRange] attribute unsigned short? anticipatedConcurrentIncomingUnidirectionalStreams;
  [EnforceRange] attribute unsigned short? anticipatedConcurrentIncomingBidirectionalStreams;

  readonly attribute Promise&lt;WebTransportCloseInfo&gt; closed;
  readonly attribute Promise&lt;undefined&gt; draining;
  undefined close(optional WebTransportCloseInfo closeInfo = {});

  readonly attribute WebTransportDatagramDuplexStream datagrams;

  Promise&lt;WebTransportBidirectionalStream&gt; createBidirectionalStream(
      optional WebTransportSendStreamOptions options = {});
  /* a ReadableStream of WebTransportBidirectionalStream objects */
  readonly attribute ReadableStream incomingBidirectionalStreams;

  Promise&lt;WebTransportSendStream&gt; createUnidirectionalStream(
      optional WebTransportSendStreamOptions options = {});
  /* a ReadableStream of WebTransportReceiveStream objects */
  readonly attribute ReadableStream incomingUnidirectionalStreams;
  WebTransportSendGroup createSendGroup();

  static readonly attribute boolean supportsReliableOnly;
};

enum WebTransportReliabilityMode {
  "pending",
  "reliable-only",
  "supports-unreliable",
};
</pre>

## Internal slots ## {#webtransport-internal-slots}

A {{WebTransport}} object has the following internal slots.

<table class="data" dfn-for="WebTransport" dfn-type="attribute">
 <thead>
  <tr>
   <th>Internal Slot
   <th>Description (<em>non-normative</em>)
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>`[[SendStreams]]`</dfn>
   <td class="non-normative">An [=ordered set=] of {{WebTransportSendStream}}s owned by this {{WebTransport}}.
  </tr>
  <tr>
   <td><dfn>`[[ReceiveStreams]]`</dfn>
   <td class="non-normative">An [=ordered set=] of {{WebTransportReceiveStream}}s owned by this
   {{WebTransport}}.
  </tr>
  <tr>
   <td><dfn>`[[IncomingBidirectionalStreams]]`</dfn>
   <td class="non-normative">A {{ReadableStream}} consisting of {{WebTransportBidirectionalStream}}
   objects.
  </tr>
  <tr>
   <td><dfn>`[[IncomingUnidirectionalStreams]]`</dfn>
   <td class="non-normative">A {{ReadableStream}} consisting of {{WebTransportReceiveStream}}s.
  </tr>
  <tr>
   <td><dfn>`[[State]]`</dfn>
   <td class="non-normative">An enum indicating the state of the transport. One of `"connecting"`,
   `"connected"`, `"draining"`, `"closed"`, and `"failed"`.
  </tr>
  <tr>
   <td><dfn>`[[Ready]]`</dfn>
   <td class="non-normative">A promise fulfilled when the associated [=WebTransport session=]
   gets [=session/established=], or rejected if the [=session/establish|establishment process=]
   failed.
  </tr>
  <tr>
   <td><dfn>`[[Reliability]]`</dfn>
   <td class="non-normative">A {{WebTransportReliabilityMode}} indicating whether
   the first hop supports unreliable (UDP) transport or whether only reliable
   (TCP fallback) transport is available. Returns `"pending"` until a connection
   has been established.
  </tr>
  <tr>
   <td><dfn>`[[CongestionControl]]`</dfn>
   <td class="non-normative">A {{WebTransportCongestionControl}} indicating
   whether a preference for a congestion control algorithm optimized for
   throughput or low latency was requested by the application and satisfied
   by the user agent, or `"default"`.
  </tr>
  <tr>
   <td><dfn>`[[AnticipatedConcurrentIncomingUnidirectionalStreams]]`</dsfn>
   <td class="non-normative">The number of concurrently open
   [=incoming unidirectional=] streams the application anticipates the server creating, or null.
  </tr>
  <tr>
   <td><dfn>`[[AnticipatedConcurrentIncomingBidirectionalStreams]]`</dfn>
   <td class="non-normative">The number of concurrently open
   [=bidirectional=] streams the application anticipates the server creating, or null.
  </tr>
  <tr>
   <td><dfn>`[[Closed]]`</dfn>
   <td class="non-normative">A promise fulfilled when the associated {{WebTransport}} object is
   closed gracefully, or rejected when it is closed abruptly or failed on initialization.
  </tr>
  <tr>
   <td><dfn>`[[Draining]]`</dfn>
   <td class="non-normative">A promise fulfilled when the associated [=WebTransport session=]
   receives a [=session-signal/DRAIN_WEBTRANSPORT_SESSION=] capsule or a
   [=session-signal/GOAWAY=] frame.
  </tr>
  <tr>
   <td><dfn>`[[Datagrams]]`</dfn>
   <td class="non-normative">A {{WebTransportDatagramDuplexStream}}.
  </tr>
  <tr>
   <td><dfn>`[[Session]]`</dfn>
   <td class="non-normative">A [=WebTransport session=] for this {{WebTransport}} object, or null.
  </tr>
</table>

## Constructor ##  {#webtransport-constructor}

<div algorithm="webtransport-contructor">
When the {{WebTransport/constructor()}} constructor is invoked, the user
agent MUST run the following steps:
1. Let |baseURL| be [=this=]'s [=relevant settings object=]'s [=API base URL=].
1. Let |parsedURL| be the [=URL record=] resulting from [=URL parser|parsing=]
   {{WebTransport/constructor(url, options)/url}} with |baseURL|.
1. If |parsedURL| is a failure, [=throw=] a {{SyntaxError}} exception.
1. If |parsedURL| [=scheme=] is not `https`, [=throw=] a {{SyntaxError}} exception.
1. If |parsedURL| [=fragment=] is not null, [=throw=] a {{SyntaxError}} exception.
1. Let |allowPooling| be {{WebTransport/constructor(url, options)/options}}'s
   {{WebTransportOptions/allowPooling}}.
1. Let |dedicated| be the negation of |allowPooling|.
1. Let |serverCertificateHashes| be {{WebTransport/constructor(url, options)/options}}'s
   {{WebTransportOptions/serverCertificateHashes}} if it exists, and null otherwise.
1. If |dedicated| is false and |serverCertificateHashes| is non-null, then [=throw=] a
   {{NotSupportedError}} exception.
1. Let |requireUnreliable| be {{WebTransport/constructor(url, options)/options}}'s
   {{WebTransportOptions/requireUnreliable}}.
1. Let |congestionControl| be {{WebTransport/constructor(url, options)/options}}'s
   {{WebTransportOptions/congestionControl}}.
1. If |congestionControl| is not `"default"`, and the user agent does not support any
   congestion control algorithms that optimize for |congestionControl|, as allowed by
   [[!RFC9002]] [Section 7](https://www.rfc-editor.org/rfc/rfc9002#section-7),
   then set |congestionControl| to `"default"`.
1. Let |anticipatedConcurrentIncomingUnidirectionalStreams| be {{WebTransport/constructor(url, options)/options}}'s
   {{WebTransportOptions/anticipatedConcurrentIncomingUnidirectionalStreams}}.
1. Let |anticipatedConcurrentIncomingBidirectionalStreams| be {{WebTransport/constructor(url, options)/options}}'s
   {{WebTransportOptions/anticipatedConcurrentIncomingBidirectionalStreams}}.
1. Let |incomingDatagrams| be a [=new=] {{ReadableStream}}.
1. Let |outgoingDatagrams| be a [=new=] {{WritableStream}}.
1. Let |datagrams| be the result of [=WebTransportDatagramDuplexStream/creating=] a
   {{WebTransportDatagramDuplexStream}}, its [=WebTransportDatagramDuplexStream/create/readable=] set to
   |incomingDatagrams| and its [=WebTransportDatagramDuplexStream/create/writable=] set to |outgoingDatagrams|.
1. Let |transport| be a newly constructed {{WebTransport}} object, with:
    : {{[[SendStreams]]}}
    :: an empty [=ordered set=]
    : {{[[ReceiveStreams]]}}
    :: an empty [=ordered set=]
    : {{[[IncomingBidirectionalStreams]]}}
    :: a new {{ReadableStream}}
    : {{[[IncomingUnidirectionalStreams]]}}
    :: a new {{ReadableStream}}
    : {{[[State]]}}
    :: `"connecting"`
    : {{[[Ready]]}}
    :: a new promise
    : {{[[Reliability]]}}
    :: "pending"
    : {{[[CongestionControl]]}}
    :: |congestionControl|
    : {{[[AnticipatedConcurrentIncomingUnidirectionalStreams]]}}
    :: |anticipatedConcurrentIncomingUnidirectionalStreams|
    : {{[[AnticipatedConcurrentIncomingBidirectionalStreams]]}}
    :: |anticipatedConcurrentIncomingBidirectionalStreams|
    : {{[[Closed]]}}
    :: a new promise
    : {{[[Draining]]}}
    :: a new promise
    : {{[[Datagrams]]}}
    :: |datagrams|
    : {{[[Session]]}}
    :: null
1. Let |pullDatagramsAlgorithm| be an action that runs [=pullDatagrams=] with |transport|.
1. Let |writeDatagramsAlgorithm| be an action that runs [=writeDatagrams=] with |transport|.

Note: Using 64kB buffers with datagrams is recommended because the effective
maximum WebTransport datagram frame size has an upper bound of the QUIC maximum datagram frame size
which is recommended to be 64kB (See [[!QUIC-DATAGRAM]] [Section 3](https://datatracker.ietf.org/doc/html/rfc9221#section-3)).
This will ensure the stream is not errored due to a datagram being larger than the buffer.

1. [=ReadableStream/Set up with byte reading support=] |incomingDatagrams| with
   [=ReadableStream/set up with byte reading support/pullAlgorithm=] set to
   |pullDatagramsAlgorithm|, and [=ReadableStream/set up/highWaterMark=] set to 0.
1. [=WritableStream/Set up=] |outgoingDatagrams| with [=WritableStream/set up/writeAlgorithm=]
   set to |writeDatagramsAlgorithm|.
1. Let |pullBidirectionalStreamAlgorithm| be an action that runs [=pullBidirectionalStream=]
   with |transport|.
1. [=ReadableStream/Set up=] |transport|.{{[[IncomingBidirectionalStreams]]}} with
   [=ReadableStream/set up/pullAlgorithm=] set to |pullBidirectionalStreamAlgorithm|, and
   [=ReadableStream/set up/highWaterMark=] set to 0.
1. Let |pullUnidirectionalStreamAlgorithm| be an action that runs [=pullUnidirectionalStream=]
   with |transport|.
1. [=ReadableStream/Set up=] |transport|.{{[[IncomingUnidirectionalStreams]]}} with
   [=ReadableStream/set up/pullAlgorithm=] set to |pullUnidirectionalStreamAlgorithm|, and
   [=ReadableStream/set up/highWaterMark=] set to 0.
1. [=Initialize WebTransport over HTTP=] with |transport|, |parsedURL|, |dedicated|,
   |requireUnreliable|, |congestionControl|, and |serverCertificateHashes|.
1. Return |transport|.

</div>

<div algorithm>
To <dfn>initialize WebTransport over HTTP</dfn>, given a {{WebTransport}} object
<var>transport</var>, a [=URL record=] |url|, a boolean |dedicated|, a boolean
|requireUnreliable|, a {{WebTransportCongestionControl}} |congestionControl|, and a
sequence&lt;{{WebTransportHash}}&gt; |serverCertificateHashes|, run these steps.

1. Let |client| be |transport|'s [=relevant settings object=].
1. Let |origin| be |client|'s [=environment settings object/origin=].
1. Let |request| be a new [=/request=] whose [=request/URL=] is |url|, [=request/client=] is
   |client|, [=request/policy container=] is |client|'s
   [=environment settings object/policy container=], [=request/destination=] is an empty string,
   and [=request/origin=] is |origin|.
1. Run <a>report Content Security Policy violations for |request|</a>.
1. If [=should request be blocked by Content Security Policy?=] with |request| returns
   <b>"Blocked"</b>, or if |request| [=block bad port|should be blocked due to a bad port=]
   returns <b>blocked</b>, then abort the remaining steps and [=queue a network task=] with |transport|
   to run these steps:
  1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, then abort these steps.
  1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
     {{WebTransportErrorOptions/source}} is `"session"`.
  1. [=Cleanup=] |transport| with |error|.
1. Let |networkPartitionKey| be the result of [=determining the network partition key=] with
   |transport|'s [=relevant settings object=].
1. Run the following steps [=in parallel=], but [=abort when=] |transport|.{{[[State]]}} becomes `"closed"` or `"failed"`:
  1. Let |newConnection| be "`no`" if |dedicated| is false; otherwise "`yes-and-dedicated`".
  1. Let |connection| be the result of [=obtain a connection|obtaining a connection=] with
     |networkPartitionKey|, |url|, false, |newConnection|, and |requireUnreliable|. If the user agent
     supports more than one congestion control algorithm, choose one appropriate for
     |congestionControl| for sending of data on this |connection|. When obtaining a connection, if
     |serverCertificateHashes| is specified, instead of using the default certificate verification
     algorithm, consider the certificate valid if it meets the [=custom certificate
     requirements=] and if [=verify a certificate hash|verifying the certificate hash=] against
     |serverCertificateHashes| returns true. If either condition is not met, let |connection| be
     failure.
  1. If |connection| is failure, then abort the remaining steps and [=queue a network task=] with
     |transport| to run these steps:
    1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, then abort these steps.
    1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
       {{WebTransportErrorOptions/source}} is `"session"`.
    1. [=Cleanup=] |transport| with |error|.
  1. Wait for |connection| to receive the first SETTINGS frame, and let |settings| be a dictionary that
     represents the SETTINGS frame.
  1. If |settings| doesn't contain SETTINGS_ENABLE_WEBTRANPORT with a value of 1, or it doesn't
     contain H3_DATAGRAM with a value of 1, then abort the remaining steps and [=queue a network
     task=] with |transport| to run these steps:
    1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, then abort these steps.
    1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
       {{WebTransportErrorOptions/source}} is `"session"`.
    1. [=Cleanup=] |transport| with |error|.
  1. [=session/Establish=] a [=WebTransport session=] with |origin| on |connection|.

    Note: This step also contains the transport parameter exchange specified in [[!QUIC-DATAGRAM]].

  1. If the previous step fails, abort the remaining steps and [=queue a network task=] with
     |transport| to run these steps:
    1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, then abort these steps.
    1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
       {{WebTransportErrorOptions/source}} is `"session"`.
    1. [=Cleanup=] |transport| with |error|.
  1. Let |session| be the established [=WebTransport session=].
  1. Assert: |maxDatagramSize| is an integer.
  1. [=Queue a network task=] with |transport| to run these steps:
    1. If |transport|.{{[[State]]}} is not `"connecting"`:
      1. [=In parallel=], [=session/terminate=] |session|.
      1. Abort these steps.
    1. Set |transport|.{{[[State]]}} to `"connected"`.
    1. Set |transport|.{{[[Session]]}} to |session|.
    1. If the connection is an HTTP/3 connection, set |transport|'s {{[[Reliability]]}} to `"supports-unreliable"`.
    1. If the connection is an HTTP/2 connection [[!WEB-TRANSPORT-HTTP2]], set |transport|'s {{[[Reliability]]}} to `"reliable-only"`.
    1. [=Resolve=] |transport|.{{[[Ready]]}} with undefined.

</div>

<div algorithm="pullBidirectionalStream">
To <dfn>pullBidirectionalStream</dfn>, given a {{WebTransport}} object <var>transport</var>, run
these steps.

1. If |transport|.{{[[State]]}} is `"connecting"`, then return the result of performing the
   following steps [=upon fulfillment=] of |transport|.{{[[Ready]]}}:
  1. Return the result of [=pullBidirectionalStream=] with |transport|.
1. If |transport|.{{[[State]]}} is not `"connected"`, then return a new [=rejected=] promise with
   an {{InvalidStateError}}.
1. Let |session| be |transport|.{{[[Session]]}}.
1. Let |p| be a new promise.
1. Run the following steps [=in parallel=]:
  1. Wait until there is an [=session/receive a bidirectional stream|available incoming bidirectional
     stream=].
  1. Let |internalStream| be the result of [=session/receiving a bidirectional stream=].
  1. [=Queue a network task=] with |transport| to run these steps:
    1. Let |stream| be the result of [=BidirectionalStream/creating=] a
       {{WebTransportBidirectionalStream}} with |internalStream| and |transport|.
    1. [=ReadableStream/Enqueue=] |stream| to |transport|.{{[[IncomingBidirectionalStreams]]}}.
    1. [=Resolve=] |p| with undefined.
1. Return |p|.

</div>

<div algorithm>
To <dfn>pullUnidirectionalStream</dfn>, given a {{WebTransport}} object <var>transport</var>, run
these steps.

1. If |transport|.{{[[State]]}} is `"connecting"`, then return the result of performing the
   following steps [=upon fulfillment=] of |transport|.{{[[Ready]]}}:
  1. Return the result of [=pullUnidirectionalStream=] with |transport|.
1. If |transport|.{{[[State]]}} is not `"connected"`, then return a new [=rejected=] promise with
   an {{InvalidStateError}}.
1. Let |session| be |transport|.{{[[Session]]}}.
1. Let |p| be a new promise.
1. Run the following steps [=in parallel=]:
  1. Wait until there is an
     [=session/receive an incoming unidirectional stream|available incoming unidirectional stream=].
  1. Let |internalStream| be the result of [=session/receiving an incoming unidirectional stream=].
  1. [=Queue a network task=] with |transport| to run these steps:
    1. Let |stream| be the result of [=WebTransportReceiveStream/creating=] a {{WebTransportReceiveStream}} with
       |internalStream| and |transport|.
    1. [=ReadableStream/Enqueue=] |stream| to |transport|.{{[[IncomingUnidirectionalStreams]]}}.
    1. [=Resolve=] |p| with undefined.
1. Return |p|.

</div>

## Attributes ##  {#webtransport-attributes}

: <dfn for="WebTransport" attribute>ready</dfn>
:: On getting, it MUST return [=this=]'s {{[[Ready]]}}.
: <dfn for="WebTransport" attribute>closed</dfn>
:: On getting, it MUST return [=this=]'s {{[[Closed]]}}.
: <dfn for="WebTransport" attribute>draining</dfn>
:: On getting, it MUST return [=this=]'s {{[[Draining]]}}.
: <dfn for="WebTransport" attribute>datagrams</dfn>
:: A single duplex stream for sending and receiving datagrams over this session.
   The getter steps for the `datagrams` attribute SHALL be:
     1. Return [=this=]'s {{[[Datagrams]]}}.
: <dfn for="WebTransport" attribute>incomingBidirectionalStreams</dfn>
:: Returns a {{ReadableStream}} of {{WebTransportBidirectionalStream}}s that have been
   received from the server.
   The getter steps for the `incomingBidirectionalStreams` attribute SHALL be:
     1. Return [=this=]'s {{[[IncomingBidirectionalStreams]]}}.
: <dfn for="WebTransport" attribute>incomingUnidirectionalStreams</dfn>
:: A {{ReadableStream}} of unidirectional streams, each represented by a
   {{WebTransportReceiveStream}}, that have been received from the server.
   The getter steps for `incomingUnidirectionalStreams` are:
     1. Return [=this=].{{[[IncomingUnidirectionalStreams]]}}.

: <dfn for="WebTransport" attribute>reliability</dfn>
:: Whether connection supports unreliable (over UDP) transport or only reliable
   (over TCP fallback) transport.
   Returns `"pending"` until a connection has been established.
   The getter steps are to return [=this=]'s {{[[Reliability]]}}.

: <dfn for="WebTransport" attribute>congestionControl</dfn>
:: The application's preference, if requested in the constructor, and satisfied
   by the user agent, for a congestion control algorithm optimized for either
   throughput or low latency for sending on this connection. If a preference was
   requested but not satisfied, then the value is `"default"`
   The getter steps are to return [=this=]'s {{[[CongestionControl]]}}.

: <dfn for="WebTransport" attribute>supportsReliableOnly</dfn>
:: Returns true if the user agent supports [=WebTransport sessions=] over exclusively reliable
   [=connections=], otherwise false.

: <dfn for="WebTransport" attribute>anticipatedConcurrentIncomingUnidirectionalStreams</dfn>
:: Optionally lets an application specify the number of concurrently open
   [=incoming unidirectional=] streams it anticipates the server creating.
   If not null, the user agent SHOULD attempt to reduce future round-trips by taking
   {{[[AnticipatedConcurrentIncomingUnidirectionalStreams]]}} into consideration in its
   negotiations with the server.

   The getter steps are to return [=this=]'s {{[[AnticipatedConcurrentIncomingUnidirectionalStreams]]}}.

   The setter steps, given |value|, are to set [=this=]'s
   {{[[AnticipatedConcurrentIncomingUnidirectionalStreams]]}} to |value|.

: <dfn for="WebTransport" attribute>anticipatedConcurrentIncomingBidirectionalStreams</dfn>
:: Optionally lets an application specify the number of concurrently open
   [=bidirectional=] streams it anticipates the server creating.
   If not null, the user agent SHOULD attempt to reduce future round-trips by taking
   {{[[AnticipatedConcurrentIncomingBidirectionalStreams]]}} into consideration in its
   negotiations with the server.

   The getter steps are to return [=this=]'s {{[[AnticipatedConcurrentIncomingBidirectionalStreams]]}}.

   The setter steps, given |value|, are to set [=this=]'s
   {{[[AnticipatedConcurrentIncomingBidirectionalStreams]]}} to |value|.

Note: Setting {{WebTransport/anticipatedConcurrentIncomingUnidirectionalStreams}} or
{{WebTransport/anticipatedConcurrentIncomingBidirectionalStreams}} does not guarantee
the application will receive the number of streams it anticipates.

## Methods ##  {#webtransport-methods}

<div algorithm>

: <dfn for="WebTransport" method>close(closeInfo)</dfn>
:: Terminates the [=WebTransport session=] associated with the WebTransport object.

   When close is called, the user agent MUST run the following steps:
     1. Let |transport| be [=this=].
     1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, then abort these steps.
     1. If |transport|.{{[[State]]}} is `"connecting"`:
       1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
          {{WebTransportErrorOptions/source}} is `"session"`.
       1. [=Cleanup=] |transport| with |error|.
       1. Abort these steps.
     1. Let |session| be |transport|.{{[[Session]]}}.
     1. Let |code| be |closeInfo|.{{WebTransportCloseInfo/closeCode}}.
     1. Let |reasonString| be the maximal [=code unit prefix=] of
        |closeInfo|.{{WebTransportCloseInfo/reason}} where the [=byte sequence/length=] of the
        [=UTF-8 encoded=] prefix doesn't exceed 1024.
     1. Let |reason| be |reasonString|, [=UTF-8 encoded=].
     1. [=In parallel=], [=session/terminate=] |session| with |code| and |reason|.

       Note: This also [=stream/resets=] or [=sends STOP_SENDING=] [=WebTransport streams=] contained in
       |transport|.{{[[SendStreams]]}} and {{[[ReceiveStreams]]}}.
     1. [=Cleanup=] |transport| with {{AbortError}} and |closeInfo|.

</div>

: <dfn for="WebTransport" method>getStats()</dfn>
:: Gathers stats for this {{WebTransport}}'s [=underlying connection=]
   and reports the result asynchronously.</p>

   When getStats is called, the user agent MUST run the following steps:
     1. Let |transport| be [=this=].
     1. Let |p| be a new promise.
     1. If |transport|.{{[[State]]}} is `"failed"`, [=reject=] |p| with an
        {{InvalidStateError}} and abort these steps.
     1. Run the following steps [=in parallel=]:
         1. If |transport|.{{[[State]]}} is `"connecting"`, wait until it changes.
         1. If |transport|.{{[[State]]}} is `"failed"`, [=reject=] |p| with an
            {{InvalidStateError}} and abort these steps.
	 1. If |transport|.{{[[State]]}} is `"closed"`, [=resolve=] |p| with
	    the most recent stats available for the connection and abort these
	    steps. The exact point at which those stats are collected is
	    [=implementation-defined=].
	 1. Gather the stats from the [=underlying connection=], including stats on datagrams.
         1. [=Queue a network task=] with |transport| to run the following steps:
           1. Let |stats| be a [=new=] {{WebTransportConnectionStats}} object representing the gathered stats.
           1. [=Resolve=] |p| with |stats|.
     1. Return |p|.

: <dfn for="WebTransport" method>createBidirectionalStream()</dfn>
:: Creates a {{WebTransportBidirectionalStream}} object for an outgoing bidirectional
   stream.  Note that the mere creation of a stream is not immediately visible to the peer until
   it is used to send data.

   When `createBidirectionalStream` is called, the user agent MUST run the
   following steps:

   1. Let |transport| be [=this=].
   1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`,
      return a new [=rejected=] promise with an {{InvalidStateError}}.
   1. Let |sendGroup| be {{WebTransport/createBidirectionalStream(options)/options}}'s
      {{WebTransportSendStreamOptions/sendGroup}}.
   1. Let |sendOrder| be {{WebTransport/createBidirectionalStream(options)/options}}'s
      {{WebTransportSendStreamOptions/sendOrder}}.
   1. Let |waitUntilAvailable| be {{WebTransport/createBidirectionalStream(options)/options}}'s
      {{WebTransportSendStreamOptions/waitUntilAvailable}}.
   1. Let |p| be a new promise.
   1. Run the following steps [=in parallel=], but [=abort when=] |transport|'s
      {{[[State]]}} becomes `"closed"` or `"failed"`, and instead
      [=queue a network task=] with |transport| to [=reject=] |p| with an {{InvalidStateError}}:
      1. Let |streamId| be a new stream ID that is valid and unique for
         |transport|.{{[[Session]]}}, as defined in [[!QUIC]]
         [Section 19.11](https://www.rfc-editor.org/rfc/rfc9000#section-19.11). If one is not
         immediately available due to exhaustion, wait for it to become
         available if |waitUntilAvailable| is true, [=reject=] |p| with a
         {{QuotaExceededError}} and abort these steps otherwise.
      1. Let |internalStream| be the result of [=creating a bidirectional stream=] with
           |transport|.{{[[Session]]}} and |streamId|.
      1. [=Queue a network task=] with |transport| to run the following steps:
        1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`,
           [=reject=] |p| with an {{InvalidStateError}} and abort these steps.
        1. Let |stream| be the result of [=BidirectionalStream/creating=] a
           {{WebTransportBidirectionalStream}} with |internalStream|, |transport|, |sendGroup|, and |sendOrder|.
        1. [=Resolve=] |p| with |stream|.
   1. Return |p|.

: <dfn for="WebTransport" method>createUnidirectionalStream()</dfn>

:: Creates a {{WebTransportSendStream}} for an outgoing unidirectional stream.  Note
   that the mere creation of a stream is not immediately visible to the server until it is used
   to send data.

   When `createUnidirectionalStream()` method is called, the user agent MUST
   run the following steps:
     1. Let |transport| be [=this=].
     1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`,
        return a new [=rejected=] promise with an {{InvalidStateError}}.
     1. Let |sendGroup| be {{WebTransport/createUnidirectionalStream(options)/options}}'s
        {{WebTransportSendStreamOptions/sendGroup}}.
     1. Let |sendOrder| be {{WebTransport/createUnidirectionalStream(options)/options}}'s
        {{WebTransportSendStreamOptions/sendOrder}}.
     1. Let |waitUntilAvailable| be {{WebTransport/createUnidirectionalStream(options)/options}}'s
        {{WebTransportSendStreamOptions/waitUntilAvailable}}.
     1. Let |p| be a new promise.
     1. Run the following steps [=in parallel=], but [=abort when=] |transport|'s
        {{[[State]]}} becomes `"closed"` or `"failed"`, and instead
        [=queue a network task=] with |transport| to [=reject=] |p| with an {{InvalidStateError}}:
        1. Let |streamId| be a new stream ID that is valid and unique for
           |transport|.{{[[Session]]}}, as defined in [[!QUIC]]
           [Section 19.11](https://www.rfc-editor.org/rfc/rfc9000#section-19.11). If one is not
           immediately available due to exhaustion, wait for it to become
           available if |waitUntilAvailable| is true, [=reject=] |p| with a
           {{QuotaExceededError}} and abort these steps otherwise.
        1. Let |internalStream| be the result of [=creating an outgoing unidirectional stream=] with
           |transport|.{{[[Session]]}} and |streamId|.
        1. [=Queue a network task=] with |transport| to run the following steps:
          1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`,
             [=reject=] |p| with an {{InvalidStateError}} and abort these steps.
          1. Let |stream| be the result of [=WebTransportSendStream/creating=] a {{WebTransportSendStream}} with
             |internalStream|, |transport|, |sendGroup|, and |sendOrder|.
          1. [=Resolve=] |p| with |stream|.
     1. return |p|.

: <dfn for="WebTransport" method>createSendGroup()</dfn>

:: Creates a {{WebTransportSendGroup}}.

   When `createSendGroup()` method is called, the user agent MUST
   run the following steps:
     1. Let |transport| be [=this=].
     1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`,
        [=throw=] an {{InvalidStateError}}.
     1. Return the result of [=WebTransportSendGroup/creating=] a {{WebTransportSendGroup}} with |transport|.

## Procedures ##  {#webtransport-procedures}

<div algorithm="cleanup a WebTransport">
To <dfn for="WebTransport">cleanup</dfn> a {{WebTransport}} |transport| with |error| and
optionally |closeInfo|, run these steps:
1. Let |sendStreams| be a copy of |transport|.{{[[SendStreams]]}}.
1. Let |receiveStreams| be a copy of |transport|.{{[[ReceiveStreams]]}}.
1. Let |outgoingDatagrams| be |transport|.{{[[Datagrams]]}}.{{WebTransportDatagramDuplexStream/[[Writable]]}}.
1. Let |incomingDatagrams| be |transport|.{{[[Datagrams]]}}.{{WebTransportDatagramDuplexStream/[[Readable]]}}.
1. Let |ready| be |transport|.{{[[Ready]]}}.
1. Let |closed| be |transport|.{{[[Closed]]}}.
1. Let |incomingBidirectionalStreams| be |transport|.{{[[IncomingBidirectionalStreams]]}}.
1. Let |incomingUnidirectionalStreams| be |transport|.{{[[IncomingUnidirectionalStreams]]}}.
1. Set |transport|.{{[[SendStreams]]}} to an empty [=set=].
1. Set |transport|.{{[[ReceiveStreams]]}} to an empty [=set=].
1. Set |transport|.{{[[Datagrams]]}}.{{WebTransportDatagramDuplexStream/[[OutgoingDatagramsQueue]]}}
   to an empty [=queue=].
1. Set |transport|.{{[[Datagrams]]}}.{{WebTransportDatagramDuplexStream/[[IncomingDatagramsQueue]]}}
   to an empty [=queue=].
1. If |closeInfo| is given, then set |transport|.{{[[State]]}} to `"closed"`.
   Otherwise, set |transport|.{{[[State]]}} to `"failed"`.
1. [=For each=] |stream| in |sendStreams|, run the following steps:
  1. If |stream|.{{[[PendingOperation]]}} is not null, reject |stream|.{{[[PendingOperation]]}}
     with |error|.
  1. [=WritableStream/Error=] |stream| with |error|.
1. [=For each=] |stream| in |receiveStreams|, [=ReadableStream/error=] |stream|
   with |error|.

  Note: Script authors can inject code which runs in Promise resolution synchronously. Hence
  from here, do not touch |transport| as it may be mutated by scripts in an unpredictable way.
  This applies to logic calling this procedure, too.

1. If |closeInfo| is given, then:
  1. [=Resolve=] |closed| with |closeInfo|.
  1. Assert: |ready| is [=settled=].
  1. [=ReadableStream/Close=] |incomingBidirectionalStreams|.
  1. [=ReadableStream/Close=] |incomingUnidirectionalStreams|.
  1. [=WritableStream/Close=] |outgoingDatagrams|.
  1. [=ReadableStream/Close=] |incomingDatagrams|.
1. Otherwise:
  1. [=Reject=] |closed| with |error|.
  1. Set |closed|.`[[PromiseIsHandled]]` to true.
  1. [=Reject=] |ready| with |error|.
  1. Set |ready|.`[[PromiseIsHandled]]` to true.
  1. [=ReadableStream/Error=] |incomingBidirectionalStreams| with |error|.
  1. [=ReadableStream/Error=] |incomingUnidirectionalStreams| with |error|.
  1. [=WritableStream/Error=] |outgoingDatagrams| with |error|.
  1. [=ReadableStream/Error=] |incomingDatagrams| with |error|.

</div>

<div algorithm>

To <dfn for="WebTransport">queue a network task</dfn> with a {{WebTransport}} |transport| and a
series of steps |steps|, run these steps:
1. [=Queue a global task=] on the [=network task source=] with |transport|'s
   [=relevant global object=] to run |steps|.

</div>

## Session termination not initiated by the client ## {#web-transport-termination}

<div algorithm="termination-initiated-by-server">
Whenever a [=WebTransport session=] which is associated with a {{WebTransport}} |transport| is
[=session/terminated=] with optionally |code| and |reasonBytes|, run these steps:

1. [=Queue a network task=] with |transport| to run these steps:
  1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, abort these steps.
  1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
     {{WebTransportErrorOptions/source}} is `"session"`.
  1. Let |closeInfo| be a [=new=] {{WebTransportCloseInfo}}.
  1. If |code| is given, set |closeInfo|'s {{WebTransportCloseInfo/closeCode}} to |code|.
  1. If |reasonBytes| is given, set |closeInfo|'s {{WebTransportCloseInfo/reason}} to |reasonBytes|,
     [=UTF-8 decoded=].
     
     Note: No language or direction metadata is available with |reasonBytes|. 
     <a href=https://www.w3.org/TR/string-meta/#firststrong>First-strong</a> heuristics can be used
     for direction when displaying the value.
  1. [=Cleanup=] |transport| with |error| and |closeInfo|.

</div>

<div algorithm="termination-caused-by-connection-error">
Whenever a {{WebTransport}} |transport|'s [=underlying connection=] gets a connection error,
run these steps:

1. [=Queue a network task=] with |transport| to run these steps:
  1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, abort these steps.
  1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
     {{WebTransportErrorOptions/source}} is `"session"`.
  1. [=Cleanup=] |transport| with |error|.

</div>

## Context cleanup steps ##  {#web-transport-context-cleanup-steps}

This specification defines <dfn>context cleanup steps</dfn> as the following steps, given
{{WebTransport}} |transport|:

1. If |transport|.{{[[State]]}} is `"connected"`, then:
  1. Set |transport|.{{[[State]]}} to `"failed"`.
  1. [=In parallel=], [=session/terminate=] |transport|.{{[[Session]]}}.
  1. [=Queue a network task=] with |transport| to run the following steps:
    1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
       {{WebTransportErrorOptions/source}} is `"session"`.
    1. [=Cleanup=] |transport| with |error|.
1. If |transport|.{{[[State]]}} is `"connecting"`, set |transport|.{{[[State]]}} to
   `"failed"`.

  Issue: This needs to be done in workers too. See
  <a href="https://www.github.com/w3c/webtransport/issues/127">#127</a> and
  <a href="https://www.github.com/whatwg/html/issues/6831">whatwg/html#6731</a>.

## Garbage Collection ## {#web-transport-gc}

A {{WebTransport}} object whose {{[[State]]}} is `"connecting"` must not be garbage collected if
{{[[IncomingBidirectionalStreams]]}}, {{[[IncomingUnidirectionalStreams]]}}, any
{{WebTransportReceiveStream}}, or {{[[Datagrams]]}}.{{WebTransportDatagramDuplexStream/[[Readable]]}}
are [=ReadableStream/locked=], or if the {{ready}}, {{draining}}, or {{closed}} promise is being observed.

A {{WebTransport}} object whose {{[[State]]}} is `"connected"` must not be garbage collected if
{{[[IncomingBidirectionalStreams]]}}, {{[[IncomingUnidirectionalStreams]]}}, any
{{WebTransportReceiveStream}}, or {{[[Datagrams]]}}.{{WebTransportDatagramDuplexStream/[[Readable]]}}
are [=ReadableStream/locked=], or if the {{draining}} or {{closed}} promise is being observed.

A {{WebTransport}} object whose {{[[State]]}} is `"draining"` must not be garbage collected if
{{[[IncomingBidirectionalStreams]]}}, {{[[IncomingUnidirectionalStreams]]}}, any
{{WebTransportReceiveStream}}, or {{[[Datagrams]]}}.{{WebTransportDatagramDuplexStream/[[Readable]]}}
are [=ReadableStream/locked=], or if the {{closed}} promise is being observed.

A {{WebTransport}} object with an [=session/established=] [=WebTransport session=]
that has data queued to be transmitted to the network, including datagrams in
{{[[Datagrams]]}}.{{[[OutgoingDatagramsQueue]]}}, must not be garbage collected.

If a {{WebTransport}} object is garbage collected while the [=underlying connection=]
is still open, the user agent must 
<a href="https://www.ietf.org/archive/id/draft-ietf-webtrans-overview-06.html#section-4.1-2.4.1">terminate the WebTransport session</a>
with an Application Error Code of `0` and Application Error Message of `""`.

## Configuration ##  {#web-transport-configuration}

<pre class="idl">
dictionary WebTransportHash {
  DOMString algorithm;
  BufferSource value;
};

dictionary WebTransportOptions {
  boolean allowPooling = false;
  boolean requireUnreliable = false;
  sequence&lt;WebTransportHash&gt; serverCertificateHashes;
  WebTransportCongestionControl congestionControl = "default";
  [EnforceRange] unsigned short? anticipatedConcurrentIncomingUnidirectionalStreams = null;
  [EnforceRange] unsigned short? anticipatedConcurrentIncomingBidirectionalStreams = null;
};

enum WebTransportCongestionControl {
  "default",
  "throughput",
  "low-latency",
};
</pre>

<dfn dictionary>WebTransportOptions</dfn> is a dictionary of parameters
that determine how the [=WebTransport session=] is established and used.

: <dfn for="WebTransportOptions" dict-member>allowPooling</dfn>
:: When set to true, the [=WebTransport session=] can be pooled, that is, its [=underlying connection=]
   can be shared with other WebTransport sessions.

: <dfn for="WebTransportOptions" dict-member>requireUnreliable</dfn>
:: When set to true, the [=WebTransport session=] cannot be established over an
   HTTP/2 [=connection=] if an HTTP/3 [=connection=] is not possible.

: <dfn for="WebTransportOptions" dict-member>serverCertificateHashes</dfn>
:: This option is only supported for transports using dedicated connections.
   For transport protocols that do not support this feature, having this
   field non-empty SHALL result in a {{NotSupportedError}} exception being thrown.
:: If supported and non-empty, the user agent SHALL deem a server certificate
   trusted if and only if it can successfully [=verify a certificate hash=] against
   {{WebTransportOptions/serverCertificateHashes}}
   and satisfies [=custom certificate requirements=].  The user agent SHALL
   ignore any hash that uses an unknown {{WebTransportHash/algorithm}}.
   If empty, the user agent SHALL use certificate verification procedures it would
   use for normal [=fetch=] operations.
:: This cannot be used with {{WebTransportOptions/allowPooling}}.

: <dfn for="WebTransportOptions" dict-member>congestionControl</dfn>
:: Optionally specifies an application's preference for a congestion control
   algorithm tuned for either throughput or low-latency to be used when sending
   data over this connection. This is a hint to the user agent.
   <div class="issue atrisk">
     <p>
       This configuration option is considered a feature at risk due to the lack
       of implementation in browsers of a congestion control algorithm,
       at the time of writing, that optimizes for low latency.
     </p>
   </div>

: <dfn for="WebTransportOptions" dict-member>anticipatedConcurrentIncomingUnidirectionalStreams</dfn>
:: Optionally lets an application specify the number of concurrently open
   [=incoming unidirectional=] streams it anticipates the server creating.
   The user agent MUST initially allow at least 100 [=incoming unidirectional=]
   streams from the server.
   If not null, the user agent SHOULD attempt to reduce round-trips by taking
   {{[[AnticipatedConcurrentIncomingUnidirectionalStreams]]}} into consideration in its
   negotiations with the server.

: <dfn for="WebTransportOptions" dict-member>anticipatedConcurrentIncomingBidirectionalStreams</dfn>
:: Optionally lets an application specify the number of concurrently open
   [=bidirectional=] streams it anticipates a server creating.
   The user agent MUST initially allow the server to create at least 100
   [=bidirectional=] streams.
   If not null, the user agent SHOULD attempt to reduce round-trips by taking
   {{[[AnticipatedConcurrentIncomingBidirectionalStreams]]}} into consideration in its
   negotiations with the server.

<div algorithm>
To <dfn>compute a certificate hash</dfn>, given a |certificate|, perform the following steps:
1. Let |cert| be |certificate|, represented as a DER encoding of
   Certificate message defined in [[!RFC5280]].
1. Compute the SHA-256 hash of |cert| and return the computed value.

</div>

<div algorithm>
To <dfn>verify a certificate hash</dfn>, given a |certificate| and an array of hashes |hashes|,
perform the following steps:
1. Let |referenceHash| be the result of [=computing a certificate hash=] with |certificate|.
1. For every hash |hash| in |hashes|:
   1. If |hash|.{{WebTransportHash/value}} is not null and |hash|.{{WebTransportHash/algorithm}}
      is an [=ASCII case-insensitive=] match with "sha-256":
     1. Let |hashValue| be the byte sequence which |hash|.{{WebTransportHash/value}} represents.
     1. If |hashValue| is equal to |referenceHash|, return true.
1. Return false.

</div>

The <dfn>custom certificate requirements</dfn> are as follows: the certificate
MUST be an X.509v3 certificate as defined in [[!RFC5280]], the key used in the
Subject Public Key field MUST be one of the [=allowed public key algorithms=],
the current time MUST be within the validity period of the certificate as
defined in Section 4.1.2.5 of [[!RFC5280]] and the total length of the validity
period MUST NOT exceed two weeks.  The user agent MAY impose additional
[=implementation-defined=] requirements on the certificate.

The exact list of <dfn>allowed public key algorithms</dfn> used in the Subject
Public Key Info field (and, as a consequence, in the TLS CertificateVerify
message) is [=implementation-defined=]; however, it MUST include ECDSA with the
secp256r1 (NIST P-256) named group ([[!RFC3279]], Section 2.3.5; [[!RFC8422]])
to provide an interoperable default.  It MUST NOT contain RSA keys
([[!RFC3279]], Section 2.3.1).

## `WebTransportCloseInfo` Dictionary ##  {#web-transport-close-info}

The <dfn dictionary>WebTransportCloseInfo</dfn> dictionary includes information
relating to the error code for closing a {{WebTransport}}. This
information is used to set the error code and reason for a CONNECTION_CLOSE
frame.

<pre class="idl">
dictionary WebTransportCloseInfo {
  unsigned long closeCode = 0;
  USVString reason = "";
};
</pre>

The dictionary SHALL have the following attributes:

: <dfn for="WebTransportCloseInfo" dict-member>closeCode</dfn>
:: The error code communicated to the peer.
: <dfn for="WebTransportCloseInfo" dict-member>reason</dfn>
:: The reason for closing the {{WebTransport}}.

## `WebTransportSendStreamOptions` Dictionary ##  {#uni-stream-options}

The <dfn dictionary>WebTransportSendStreamOptions</dfn> is a
dictionary of parameters that affect how {{WebTransportSendStream}}s created by
{{WebTransport/createUnidirectionalStream}} and
{{WebTransport/createBidirectionalStream}} behave.

<pre class="idl">
dictionary WebTransportSendStreamOptions {
  WebTransportSendGroup? sendGroup = null;
  long long sendOrder = 0;
  boolean waitUntilAvailable = false;
};
</pre>

The dictionary SHALL have the following attributes:

: <dfn for="WebTransportSendStreamOptions" dict-member>sendGroup</dfn>
:: An optional {{WebTransportSendGroup}} to [=group=] this
   {{WebTransportSendStream}} under, or null.

: <dfn for="WebTransportSendStreamOptions" dict-member>sendOrder</dfn>
:: A send order number that, if provided, opts the created
   {{WebTransportSendStream}} in to participating in <dfn>strict ordering</dfn>.
   Bytes currently queued on [=strict ordering|strictly ordered=]
   {{WebTransportSendStream}}s will be sent ahead of bytes currently queued on
   other [=strict ordering|strictly ordered=] {{WebTransportSendStream}}s
   created with lower send order numbers.

   If no send order number is provided, then the order in which the
   user agent sends bytes from it relative to other {{WebTransportSendStream}}s
   is [=implementation-defined=]. User agents are strongly encouraged however to
   divide bandwidth fairly between all streams that aren't starved by lower send
   order numbers.

   Note: This is sender-side data prioritization which does not guarantee
   reception order.

: <dfn for="WebTransportSendStreamOptions" dict-member>waitUntilAvailable</dfn>
:: If true, the promise returned by the
   {{WebTransport/createUnidirectionalStream}} or
   {{WebTransport/createBidirectionalStream}} call will not be [=settled=]
   until either the [=underlying connection=] has sufficient flow control
   credit to create the stream, or the connection reaches a state in which no
   further outgoing streams are possible.  If false, the promise will be
   [=rejected=] if no flow control window is available at the time of the call.

## `WebTransportConnectionStats` Dictionary ##  {#web-transport-connection-stats}

The <dfn dictionary>WebTransportConnectionStats</dfn> dictionary includes information
on WebTransport-specific stats about the [=WebTransport session=]'s [=underlying connection=].

Note: When pooling is used, multiple [=WebTransport sessions=] pooled
on the same [=connection=] all receive the same information, i.e. the information
is disclosed across pooled [=WebTransport sessions | sessions=] holding the
same [[fetch#network-partition-keys|network partition key]].

Note: Any unavailable stats will be [=map/exists|absent=] from the {{WebTransportConnectionStats}} dictionary.

<pre class="idl">
dictionary WebTransportConnectionStats {
  unsigned long long bytesSent;
  unsigned long long packetsSent;
  unsigned long long bytesLost;
  unsigned long long packetsLost;
  unsigned long long bytesReceived;
  unsigned long long packetsReceived;
  DOMHighResTimeStamp smoothedRtt;
  DOMHighResTimeStamp rttVariation;
  DOMHighResTimeStamp minRtt;
  WebTransportDatagramStats datagrams;
  unsigned long long? estimatedSendRate;
};
</pre>

The dictionary SHALL have the following attributes:

: <dfn for="WebTransportConnectionStats" dict-member>bytesSent</dfn>
:: The number of bytes sent on the [=underlying connection=], including retransmissions.
   Does not include UDP or any other outer framing.
: <dfn for="WebTransportConnectionStats" dict-member>packetsSent</dfn>
:: The number of packets sent on the [=underlying connection=], including those that are determined to have been lost.
: <dfn for="WebTransportConnectionStats" dict-member>bytesLost</dfn>
:: The number of bytes lost on the [=underlying connection=] (does not monotonically increase, because packets that are declared lost can subsequently be received).
   Does not include UDP or any other outer framing.
: <dfn for="WebTransportConnectionStats" dict-member>packetsLost</dfn>
:: The number of packets lost on the [=underlying connection=] (does not monotonically increase, because packets that are declared lost can subsequently be received).
: <dfn for="WebTransportConnectionStats" dict-member>bytesReceived</dfn>
:: The number of total bytes received on the [=underlying connection=], including
   duplicate data for streams. Does not include UDP or any other outer framing.
: <dfn for="WebTransportConnectionStats" dict-member>packetsReceived</dfn>
:: The number of total packets received on the [=underlying connection=], including
   packets that were not processable.
: <dfn for="WebTransportConnectionStats" dict-member>smoothedRtt</dfn>
:: The smoothed round-trip time (RTT) currently observed on the connection, as defined
   in [[!RFC9002]] [Section 5.3](https://www.rfc-editor.org/rfc/rfc9002#section-5.3).
: <dfn for="WebTransportConnectionStats" dict-member>rttVariation</dfn>
:: The mean variation in round-trip time samples currently observed on the
   connection, as defined in [[!RFC9002]]
   [Section 5.3](https://www.rfc-editor.org/rfc/rfc9002#section-5.3).
: <dfn for="WebTransportConnectionStats" dict-member>minRtt</dfn>
:: The minimum round-trip time observed on the entire connection.
: <dfn for="WebTransportConnectionStats" dict-member>estimatedSendRate</dfn>
:: The estimated rate at which queued data will be sent by the user agent, in bits per second.
   This rate applies to all streams and datagrams that share a [=WebTransport session=]
   and is calculated by the congestion control algorithm (potentially chosen by
   {{WebTransport/congestionControl}}). If the user agent does not
   currently have an estimate, the member MUST be the `null` value.
   The member can be `null` even if it was not `null` in previous results.


## `WebTransportDatagramStats` Dictionary ##  {#web-transport-datagram-stats}

The <dfn dictionary>WebTransportDatagramStats</dfn> dictionary includes statistics
on datagram transmission over the [=underlying connection=].

<pre class="idl">
dictionary WebTransportDatagramStats {
  unsigned long long droppedIncoming;
  unsigned long long expiredIncoming;
  unsigned long long expiredOutgoing;
  unsigned long long lostOutgoing;
};
</pre>

The dictionary SHALL have the following attributes:

: <dfn for="WebTransportDatagramStats" dict-member>droppedIncoming</dfn>
:: The number of incoming datagrams that were dropped due to the application not reading
  from {{WebTransport/datagrams}}' {{WebTransportDatagramDuplexStream/readable}}
  before new datagrams overflow the receive queue.
: <dfn for="WebTransportDatagramStats" dict-member>expiredIncoming</dfn>
:: The number of incoming datagrams that were dropped due to being older than
  {{incomingMaxAge}} before they were read from {{WebTransport/datagrams}}'
  {{WebTransportDatagramDuplexStream/readable}}.
: <dfn for="WebTransportDatagramStats" dict-member>expiredOutgoing</dfn>
:: The number of datagrams queued for sending that were dropped due to being
   older than {{outgoingMaxAge}} before they were able to be sent.
: <dfn for="WebTransportDatagramStats" dict-member>lostOutgoing</dfn>
:: The number of sent datagrams that were declared lost, as defined in
   [[!RFC9002]] [Section 6.1](https://www.rfc-editor.org/rfc/rfc9002#section-6.1).

# Interface `WebTransportSendStream` #  {#send-stream}

A {{WebTransportSendStream}} is a {{WritableStream}} providing outgoing streaming
features with an [=outgoing unidirectional=] or [=bidirectional=]
[=WebTransport stream=].

It is a {{WritableStream}} of {{Uint8Array}} that can be written to, to send
data to the server.

<pre class="idl">
[Exposed=(Window,Worker), SecureContext, Transferable]
interface WebTransportSendStream : WritableStream {
  attribute WebTransportSendGroup? sendGroup;
  attribute long long sendOrder;
  Promise&lt;WebTransportSendStreamStats&gt; getStats();
  WebTransportWriter getWriter();
};
</pre>

A {{WebTransportSendStream}} is always created by the
[=WebTransportSendStream/create=] procedure.

The {{WebTransportSendStream}}'s [=transfer steps=] and
[=transfer-receiving steps=] are
[those of](https://streams.spec.whatwg.org/#ws-transfer) {{WritableStream}}.

## Attributes ##  {#send-stream-attributes}

: <dfn for="WebTransportSendStream" attribute>sendGroup</dfn>
:: The getter steps are:
     1. Return [=this=]'s {{WebTransportSendStream/[[SendGroup]]}}.
:: The setter steps, given |value|, are:
     1. If |value| is non-null, and
        |value|.{{WebTransportSendGroup/[[Transport]]}} is not
        [=this=].{{WebTransportSendStream/[[Transport]]}}, [=throw=]
        an {{InvalidStateError}}.
     1. Set [=this=].{{WebTransportSendStream/[[SendGroup]]}} to |value|.

: <dfn for="WebTransportSendStream" attribute>sendOrder</dfn>
:: The getter steps are:
     1. Return [=this=]'s {{WebTransportSendStream/[[SendOrder]]}}.
:: The setter steps, given |value|, are:
     1. Set [=this=].{{WebTransportSendStream/[[SendOrder]]}} to |value|.

## Methods ##  {#send-stream-methods}

: <dfn for="WebTransportSendStream" method>getStats()</dfn>
:: Gathers stats specific to this {{WebTransportSendStream}}'s performance,
   and reports the result asynchronously.</p>

   When getStats is called, the user agent MUST run the following steps:
     1. Let |p| be a new promise.
     1. Run the following steps [=in parallel=]:
         1. Gather the stats specific to this {{WebTransportSendStream}}.
         1. Wait for the stats to be ready.
         1. [=Queue a network task=] with |transport| to run the following steps:
           1. Let |stats| be a [=new=] {{WebTransportSendStreamStats}} object
              representing the gathered stats.
           1. [=Resolve=] |p| with |stats|.
     1. Return |p|.

: <dfn for="WebTransportSendStream" method>getWriter()</dfn>
:: This method must be implemented in the same manner as {{WritableStream/getWriter}}
   inherited from {{WritableStream}}, except in place of creating a
   {{WritableStreamDefaultWriter}}, it must instead
   [=WebTransportWriter/create=] a {{WebTransportWriter}} with [=this=].

## Internal Slots ## {#send-stream-internal-slots}

A {{WebTransportSendStream}} has the following internal slots.

<table class="data" dfn-for="WebTransportSendStream" dfn-type="attribute">
 <thead>
  <tr>
   <th>Internal Slot
   <th>Description (<em>non-normative</em>)
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>`[[InternalStream]]`</dfn>
   <td class="non-normative">An [=outgoing unidirectional=] or [=bidirectional=]
   [=WebTransport stream=].
  </tr>
  <tr>
   <td><dfn>`[[PendingOperation]]`</dfn>
   <td class="non-normative">A promise representing a pending write or close operation, or null.
  </tr>
  <tr>
   <td><dfn>`[[Transport]]`</dfn>
   <td class="non-normative">A {{WebTransport}} which owns this {{WebTransportSendStream}}.
  </tr>
  <tr>
   <td><dfn>`[[SendGroup]]`</dfn>
   <td class="non-normative">An optional {{WebTransportSendGroup}}, or null.
  </tr>
  <tr>
   <td><dfn>`[[SendOrder]]`</dfn>
   <td class="non-normative">An optional send order number, defaulting to 0.
  </tr>
  <tr>
   <td><dfn>`[[AtomicWriteRequests]]`</dfn>
   <td class="non-normative">An [=ordered set=] of promises, keeping track of the subset of
    write requests that are atomic among those queued to be processed by the underlying sink.
  </tr>
 <tbody>
</table>

## Procedures ##  {#send-stream-procedures}

<div algorithm="create a SendStream">

To <dfn export for="WebTransportSendStream" lt="create|creating">create</dfn> a
{{WebTransportSendStream}}, with an [=outgoing unidirectional=] or [=bidirectional=] [=WebTransport stream=]
|internalStream|, a {{WebTransport}} |transport|, |sendGroup|, and a
|sendOrder|, run these steps:

1. Let |stream| be a [=new=] {{WebTransportSendStream}}, with:
    : {{WebTransportSendStream/[[InternalStream]]}}
    :: |internalStream|
    : {{[[PendingOperation]]}}
    :: null
    : {{WebTransportSendStream/[[Transport]]}}
    :: |transport|
    : {{WebTransportSendStream/[[SendGroup]]}}
    :: |sendGroup|
    : {{WebTransportSendStream/[[SendOrder]]}}
    :: |sendOrder|
    : {{WebTransportSendStream/[[AtomicWriteRequests]]}}
    :: An empty [=ordered set=] of promises.
1. Let |writeAlgorithm| be an action that [=writes=] |chunk| to |stream|, given |chunk|.
1. Let |closeAlgorithm| be an action that [=closes=] |stream|.
1. Let |abortAlgorithm| be an action that [=aborts=] |stream| with |reason|, given |reason|.
1. [=WritableStream/Set up=] |stream| with [=WritableStream/set up/writeAlgorithm=] set to
   |writeAlgorithm|, [=WritableStream/set up/closeAlgorithm=] set to |closeAlgorithm|,
   [=WritableStream/set up/abortAlgorithm=] set to |abortAlgorithm|.
1. Let |abortSignal| be |stream|'s \[[controller]].\[[abortController]].\[[signal]].
1. [=AbortSignal/Add=] the following steps to |abortSignal|.
  1. Let |pendingOperation| be |stream|.{{[[PendingOperation]]}}.
  1. If |pendingOperation| is null, then abort these steps.
  1. Set |stream|.{{[[PendingOperation]]}} to null.
  1. Let |reason| be |abortSignal|'s [=AbortSignal/abort reason=].
  1. Let |promise| be the result of [=aborting=] stream with |reason|.
  1. [=Upon fulfillment=] of |promise|, [=reject=] |pendingOperation| with |reason|.
1. [=set/Append=] |stream| to |transport|.{{[[SendStreams]]}}.
1. Return |stream|.

</div>

<div algorithm>
To <dfn for="WebTransportSendStream">write</dfn> |chunk| to a {{WebTransportSendStream}} |stream|, run these steps:

1. Let |transport| be |stream|.{{WebTransportSendStream/[[Transport]]}}.
1. If |chunk| is not a {{BufferSource}}, return [=a promise rejected with=] a {{TypeError}}.
1. Let |promise| be a new promise.
1. Let |bytes| be a copy of the [=byte sequence=] which |chunk| represents.
1. Set |stream|.{{[[PendingOperation]]}} to |promise|.
1. Let |inFlightWriteRequest| be
   |stream|.<a href="https://streams.spec.whatwg.org/#writablestream-inflightwriterequest">inFlightWriteRequest</a>.
1. Let |atomic| be true if [=stream=].{{WebTransportSendStream/[[AtomicWriteRequests]]}}
   [=list/contains=] |inFlightWriteRequest|, otherwise false.
1. Run the following steps [=in parallel=]:
  1. If |atomic| is true and the current [=flow control=] window is too small for |bytes| to be sent
     in its entirety, then abort the remaining steps and [=queue a network task=] with |transport|
     to run these sub-steps:
    1. Set |stream|.{{[[PendingOperation]]}} to null.
    1. [=Abort all atomic write requests=] on |stream|.
  1. Otherwise, [=stream/send=] |bytes| on |stream|.{{WebTransportSendStream/[[InternalStream]]}}
     and wait for the operation to complete.
     This sending MAY be interleaved with sending of previously queued streams and datagrams,
     as well as streams and datagrams yet to be queued to be sent over this transport.
     Datagrams SHOULD be given priority over this sending, but not to the point of starving it.

     The user-agent MAY have a buffer to improve the transfer performance. Such a buffer
     SHOULD have a fixed upper limit, to carry the backpressure information to the user of the
     {{WebTransportSendStream}}.

     This sending MUST starve
     until all bytes queued for sending on {{WebTransportSendStream}}s with the
     same {{[[SendGroup]]}} and a higher {{[[SendOrder]]}}, that are neither
     [=WritableStream/Error | errored=] nor blocked by [=flow control=], have been
     sent.

     We access |stream|.{{[[SendOrder]]}} [=in parallel=] here. User agents SHOULD
     respond to live updates of these values during sending, though the details are
     [=implementation-defined=].

     Note: Ordering of retransmissions is [=implementation-defined=],
     but user agents are strongly encouraged to prioritize retransmissions of data with
     higher {{[[SendOrder]]}} values.

     This sending MUST NOT starve otherwise,
     except for [=flow control=] reasons or [=WritableStream/Error | error=].

     The user agent SHOULD divide bandwidth fairly between all streams that aren't starved.

     Note: The definition of fairness here is [=implementation-defined=].

  1. If the previous step failed due to a network error, abort the remaining steps.

    Note: We don't reject |promise| here because we handle network errors elsewhere, and those steps
    reject |stream|.{{[[PendingOperation]]}}.

  1. Otherwise, [=queue a network task=] with |transport|
     to run these steps:
    1. Set |stream|.{{[[PendingOperation]]}} to null.
    1. If |stream|.{{WebTransportSendStream/[[AtomicWriteRequests]]}} [=list/contains=] |inFlightWriteRequest|, [=list/remove=] |inFlightWriteRequest|.
    1. [=Resolve=] |promise| with undefined.
1. Return |promise|.

Note: The [=fulfilled|fulfillment=] of the promise returned from this algorithm (or,
{{WritableStreamDefaultWriter/write(chunk)}}) does **NOT** necessarily mean that the chunk is acked by
the server [[!QUIC]]. It may just mean that the chunk is appended to the buffer. To make sure that
the chunk arrives at the server, the server needs to send an application-level acknowledgment message.

</div>

<div algorithm>
To <dfn for="WebTransportSendStream">close</dfn> a {{WebTransportSendStream}} |stream|, run these steps:

1. Let |transport| be |stream|.{{WebTransportSendStream/[[Transport]]}}.
1. Let |promise| be a new promise.
1. [=set/Remove=] |stream| from |transport|.{{[[SendStreams]]}}.
1. Set |stream|.{{[[PendingOperation]]}} to |promise|.
1. Run the following steps [=in parallel=]:
  1. [=stream/Send=] FIN on |stream|.{{WebTransportSendStream/[[InternalStream]]}} and wait for the operation to
     complete.
  1. Wait for |stream|.{{WebTransportSendStream/[[InternalStream]]}} to enter the "Data Recvd" state. [[!QUIC]]
  1. [=Queue a network task=] with |transport| to run these steps:
    1. Set |stream|.{{[[PendingOperation]]}} to null.
    1. [=Resolve=] |promise| with undefined.
1. Return |promise|.

</div>

<div algorithm>
To <dfn for="WebTransportSendStream">abort</dfn> a {{WebTransportSendStream}} |stream| with |reason|, run these steps:

1. Let |transport| be |stream|.{{WebTransportSendStream/[[Transport]]}}.
1. Let |promise| be a new promise.
1. Let |code| be 0.
1. [=set/Remove=] |stream| from |transport|.{{[[SendStreams]]}}.
1. If |reason| is a {{WebTransportError}} and |reason|.{{WebTransportError/[[StreamErrorCode]]}} is not
   null, then set |code| to |reason|.{{WebTransportError/[[StreamErrorCode]]}}.
1. If |code| < 0, then set |code| to 0.
1. If |code| > 4294967295, then set |code| to 4294967295.

   Note: Valid values of |code| are from 0 to 4294967295 inclusive. If the [=underlying connection=] is
   using HTTP/3, the code will be encoded to a number in [0x52e4a40fa8db, 0x52e5ac983162] as decribed in
   [[!WEB-TRANSPORT-HTTP3]].

1. Run the following steps [=in parallel=]:
  1. [=stream/Reset=] |stream|.{{WebTransportSendStream/[[InternalStream]]}} with |code|.
  1. [=Queue a network task=] with |transport| to [=resolve=] |promise| with undefined.
1. Return |promise|.

</div>

<div algorithm>
To <dfn for="WebTransportSendStream">abort all atomic write requests</dfn> on a {{WebTransportSendStream}} |stream|, run these steps:
  1. Let |writeRequests| be
     |stream|.<a href="https://streams.spec.whatwg.org/#writablestream-writerequests">writeRequests</a>.
  1. Let |requestsToAbort| be [=stream=].{{WebTransportSendStream/[[AtomicWriteRequests]]}}.
  1. If |writeRequests| [=list/contains=] a promise not in |requestsToAbort|, then
     [=WritableStream/error=] |stream| with {{AbortError}}, and abort these steps.
  1. [=list/Empty=] [=stream=].{{WebTransportSendStream/[[AtomicWriteRequests]]}}.
  1. [=For each=] |promise| in |requestsToAbort|, [=reject=] |promise| with {{AbortError}}.
  1. [=In parallel=], [=for each=] |promise| in |requestsToAbort|, abort the
     [=stream/send|sending=] of bytes associated with |promise|.

</div>

## STOP_SENDING signal coming from the server ##  {#send-stream-STOP_SENDING}

<div algorithm="STOP_SENDING signal">
Whenever a [=WebTransport stream=] associated with a {{WebTransportSendStream}} |stream| gets a
[=stream-signal/STOP_SENDING=] signal from the server, run these steps:

1. Let |transport| be |stream|.{{WebTransportSendStream/[[Transport]]}}.
1. Let |code| be the application protocol error code attached to the STOP_SENDING frame. [[!QUIC]]

   Note: Valid values of |code| are from 0 to 4294967295 inclusive. If the [=underlying connection=] is
   using HTTP/3, the code will be encoded to a number in [0x52e4a40fa8db, 0x52e5ac983162] as decribed in
   [[!WEB-TRANSPORT-HTTP3]].

1. [=Queue a network task=] with |transport| to run these steps:
  1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, abort these steps.
  1. [=set/Remove=] |stream| from |transport|.{{[[SendStreams]]}}.
  1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
     {{WebTransportErrorOptions/source}} is `"stream"` and
     {{WebTransportErrorOptions/streamErrorCode}} is |code|.
  1. If |stream|.{{[[PendingOperation]]}} is not null, reject |stream|.{{[[PendingOperation]]}}
     with |error|.
  1. [=WritableStream/Error=] |stream| with |error|.

</div>

## `WebTransportSendStreamStats` Dictionary ##  {#send-stream-stats}

The <dfn dictionary>WebTransportSendStreamStats</dfn> dictionary includes information
on stats specific to one {{WebTransportSendStream}}.

<pre class="idl">
dictionary WebTransportSendStreamStats {
  unsigned long long bytesWritten;
  unsigned long long bytesSent;
  unsigned long long bytesAcknowledged;
};
</pre>

The dictionary SHALL have the following attributes:

: <dfn for="WebTransportSendStreamStats" dict-member>bytesWritten</dfn>
:: The total number of bytes the application has successfully written to this
   {{WebTransportSendStream}}. This number can only increase.
: <dfn for="WebTransportSendStreamStats" dict-member>bytesSent</dfn>
:: An indicator of progress on how many of the application bytes written to this
   {{WebTransportSendStream}} has been sent at least once. This
   number can only increase, and is always less than or equal to
   {{WebTransportSendStreamStats/bytesWritten}}.

   Note: this is progress of app data sent on a single stream only, and does not
   include any network overhead.
: <dfn for="WebTransportSendStreamStats" dict-member>bytesAcknowledged</dfn>
:: An indicator of progress on how many of the application bytes written to this
   {{WebTransportSendStream}} have been sent and acknowledged as received by
   the server using QUIC's ACK mechanism. Only sequential bytes up to, but not
   including, the first non-acknowledged byte, are counted. This number can only
   increase and is always less than or equal to {{WebTransportSendStreamStats/bytesSent}}.

   Note: This value will match {{WebTransportSendStreamStats/bytesSent}} when
   the connection is over HTTP/2.

# Interface `WebTransportSendGroup` #  {#sendGroup}

A {{WebTransportSendGroup}} is an optional organizational object that tracks
transmission of data spread across many individual
(typically [=strict ordering|strictly ordered=])
{{WebTransportSendStream}}s.

{{WebTransportSendStream}}s can, at their creation or through assignment of
their `sendGroup` attribute, be <dfn>grouped</dfn> under at most one
{{WebTransportSendGroup}} at any time. By default, they are
<dfn>ungrouped</dfn>.

The user agent considers {{WebTransportSendGroup}}s as equals when allocating
bandwidth for sending {{WebTransportSendStream}}s. Each {{WebTransportSendGroup}}
also establishes a separate numberspace for evaluating
{{WebTransportSendStreamOptions/sendOrder}} numbers.

<pre class="idl">
[Exposed=(Window,Worker), SecureContext]
interface WebTransportSendGroup {
  Promise&lt;WebTransportSendStreamStats&gt; getStats();
};
</pre>

A {{WebTransportSendGroup}} is always created by the
[=WebTransportSendGroup/create=] procedure.

## Methods ##  {#sendGroup-methods}

: <dfn for="WebTransportSendGroup" method>getStats()</dfn>
:: Aggregates stats from all {{WebTransportSendStream}}s
   [=grouped=] under [=this=] sendGroup, and reports the result
    asynchronously.</p>

   When getStats is called, the user agent MUST run the following steps:
     1. Let |p| be a new promise.
     1. Let |streams| be all {{WebTransportSendStream}}s whose
       {{WebTransportSendStream/[[SendGroup]]}} is [=this=].
     1. Run the following steps [=in parallel=]:
         1. Gather stream statistics from all streams in |streams|.
         1. [=Queue a network task=] with |transport| to run the following steps:
           1. Let |stats| be a [=new=] {{WebTransportSendStreamStats}} object
              representing the aggregate numbers of the gathered stats.
           1. [=Resolve=] |p| with |stats|.
     1. Return |p|.

## Internal Slots ## {#sendGroup-internal-slots}

A {{WebTransportSendGroup}} has the following internal slots.

<table class="data" dfn-for="WebTransportSendGroup" dfn-type="attribute">
 <thead>
  <tr>
   <th>Internal Slot
   <th>Description (<em>non-normative</em>)
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>`[[Transport]]`</dfn>
   <td class="non-normative">The {{WebTransport}} object owning this {{WebTransportSendGroup}}.
  </tr>
 <tbody>
</table>

## Procedures ##  {#sendGroup-procedures}

<div algorithm="create a SendGroup">

To <dfn export for="WebTransportSendGroup" lt="create|creating">create</dfn> a
{{WebTransportSendGroup}}, with a {{WebTransport}} |transport|, run these steps:

1. Let |sendGroup| be a [=new=] {{WebTransportSendGroup}}, with:
    : {{WebTransportSendGroup/[[Transport]]}}
    :: |transport|
1. Return |sendGroup|.

</div>


# Interface `WebTransportReceiveStream` #  {#receive-stream}

A {{WebTransportReceiveStream}} is a {{ReadableStream}} providing incoming streaming
features with an [=incoming unidirectional=] or [=bidirectional=]
[=WebTransport stream=].

It is a {{ReadableStream}} of {{Uint8Array}} that can be read from, to consume
data received from the server. {{WebTransportReceiveStream}} is a [=readable byte stream=],
and hence it allows
its consumers to use a [=BYOB reader=] as well as a [=default reader=].

<pre class="idl">
[Exposed=(Window,Worker), SecureContext, Transferable]
interface WebTransportReceiveStream : ReadableStream {
  Promise&lt;WebTransportReceiveStreamStats&gt; getStats();
};
</pre>

A {{WebTransportReceiveStream}} is always created by the
[=WebTransportReceiveStream/create=] procedure.

The {{WebTransportReceiveStream}}'s [=transfer steps=] and
[=transfer-receiving steps=] are
[those of](https://streams.spec.whatwg.org/#rs-transfer) {{ReadableStream}}.

## Methods ##  {#receive-stream-methods}

: <dfn for="WebTransportReceiveStream" method>getStats()</dfn>
:: Gathers stats specific to this {{WebTransportReceiveStream}}'s performance,
   and reports the result asynchronously.</p>

   When getStats is called, the user agent MUST run the following steps:
     1. Let |p| be a new promise.
     1. Run the following steps [=in parallel=]:
         1. Gather the stats specific to this {{WebTransportReceiveStream}}.
         1. [=Queue a network task=] with |transport| to run the following steps:
           1. Let |stats| be a [=new=] {{WebTransportReceiveStreamStats}} object
              representing the gathered stats.
           1. [=Resolve=] |p| with |stats|.
     1. Return |p|.

## Internal Slots ## {#receive-stream-internal-slots}

A {{WebTransportReceiveStream}} has the following internal slots.

<table class="data" dfn-for="WebTransportReceiveStream" dfn-type="attribute">
 <thead>
  <tr>
   <th>Internal Slot
   <th>Description (<em>non-normative</em>)
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>`[[InternalStream]]`</dfn>
   <td class="non-normative">An [=incoming unidirectional=] or [=bidirectional=]
   [=WebTransport stream=].
  </tr>
  <tr>
   <td><dfn>`[[Transport]]`</dfn>
   <td class="non-normative">The {{WebTransport}} object owning this {{WebTransportReceiveStream}}.
  </tr>
</table>

## Procedures ##  {#receive-stream-procedures}

<div algorithm>

To <dfn export for="WebTransportReceiveStream" lt="create|creating">create</dfn> a
{{WebTransportReceiveStream}}, with an [=incoming unidirectional=] or [=bidirectional=] [=WebTransport stream=]
|internalStream| and a {{WebTransport}} |transport|, run these steps:

1. Let |stream| be a [=new=] {{WebTransportReceiveStream}}, with:
    : {{WebTransportReceiveStream/[[InternalStream]]}}
    :: |internalStream|
    : {{WebTransportReceiveStream/[[Transport]]}}
    :: |transport|
1. Let |pullAlgorithm| be an action that [=pulls bytes=] from |stream|.
1. Let |cancelAlgorithm| be an action that [=WebTransportReceiveStream/cancels=] |stream| with |reason|, given
   |reason|.
1. [=ReadableStream/Set up with byte reading support=] |stream| with
   [=ReadableStream/set up with byte reading support/pullAlgorithm=] set to |pullAlgorithm| and
   [=ReadableStream/set up with byte reading support/cancelAlgorithm=] set to |cancelAlgorithm|.
1. [=set/Append=] |stream| to |transport|.{{[[ReceiveStreams]]}}.
1. Return |stream|.

</div>

<div algorithm>

To <dfn for="WebTransportReceiveStream">pull bytes</dfn> from a {{WebTransportReceiveStream}} |stream|, run these steps.

1. Let |transport| be |stream|.{{WebTransportReceiveStream/[[Transport]]}}.
1. Let |internalStream| be |stream|.{{WebTransportReceiveStream/[[InternalStream]]}}.
1. Let |promise| be a new promise.
1. Let |buffer|, |offset|, and |maxBytes| be null.
1. If |stream|'s [=ReadableStream/current BYOB request view=] for |stream| is not null:
  1. Set |offset| to |stream|'s [=ReadableStream/current BYOB request view=].\[[ByteOffset]].
  1. Set |maxBytes| to |stream|'s [=ReadableStream/current BYOB request view=]'s
     [=BufferSource/byte length=].
  1. Set |buffer| to |stream|'s [=ReadableStream/current BYOB request view=]'s
     [=BufferSource/underlying buffer=].
1. Otherwise:
  1. Set |offset| to 0.
  1. Set |maxBytes| to an [=implementation-defined=] size.
  1. Set |buffer| be a [=new=] {{ArrayBuffer}} with |maxBytes| size. If allocating the
     {{ArrayBuffer}} fails, return [=a promise rejected with=] a {{RangeError}}.
1. Run the following steps [=in parallel=]:
  1. [=ArrayBuffer/Write=] the bytes that area [=stream/receive|read=] from |internalStream| into
     |buffer| with offset |offset|, up to |maxBytes| bytes. Wait until either at least one byte is
     read or FIN is received. Let |read| be the number of read bytes, and let |hasReceivedFIN| be
     whether FIN was accompanied.

     The user-agent MAY have a buffer to improve the transfer performance. Such a buffer
     SHOULD have a fixed upper limit, to carry the backpressure information to the server.

     Note: This operation may return before filling up all of |bytes|.

  1. If the previous step failed, abort the remaining steps.

    Note: We don't reject |promise| here because we handle network errors elsewhere, and those steps
    [=ReadableStream/error=] |stream|, which rejects any read requests awaiting this pull.

  1. [=Queue a network task=] with |transport| to run these steps:

     Note: If the buffer described above is available in the [=agent/event loop=] where this procedure is
     running, the following steps may run immediately.

    1. If |read| > 0:
      1. Set |view| to a new {{Uint8Array}} with |buffer|, |offset| and |read|.
      1. [=ReadableStream/Enqueue=] |view| into |stream|.
    1. If |hasReceivedFIN| is true:
      1. [=set/Remove=] |stream| from |transport|.{{[[ReceiveStreams]]}}.
      1. [=ReadableStream/Close=] |stream|.
    1. [=Resolve=] |promise| with undefined.
1. Return |promise|.

</div>

<div algorithm>

To <dfn for="WebTransportReceiveStream">cancel</dfn> a {{WebTransportReceiveStream}} |stream| with |reason|, run these
steps.

1. Let |transport| be |stream|.{{WebTransportReceiveStream/[[Transport]]}}.
1. Let |internalStream| be |stream|.{{WebTransportReceiveStream/[[InternalStream]]}}.
1. Let |promise| be a new promise.
1. Let |code| be 0.
1. If |reason| is a {{WebTransportError}} and |reason|.{{WebTransportError/[[StreamErrorCode]]}} is not
   null, then set |code| to |reason|.{{WebTransportError/[[StreamErrorCode]]}}.
1. If |code| < 0, then set |code| to 0.
1. If |code| > 4294967295, then set |code| to 4294967295.

   Note: Valid values of |code| are from 0 to 4294967295 inclusive. If the [=underlying connection=] is
   using HTTP/3, the code will be encoded to a number in [0x52e4a40fa8db, 0x52e5ac983162] as decribed in
   [[!WEB-TRANSPORT-HTTP3]].

1. [=set/Remove=] |stream| from |transport|.{{[[SendStreams]]}}.
1. Run the following steps [=in parallel=]:
  1. [=Send STOP_SENDING=] with |internalStream| and |code|.
  1. [=Queue a network task=] with |transport| to run these steps:

    Note: If the buffer described above is available in the [=agent/event loop=] where this procedure is
    running, the following steps may run immediately.

    1. [=set/Remove=] |stream| from |transport|.{{[[ReceiveStreams]]}}.
    1. [=Resolve=] |promise| with undefined.
1. Return |promise|.

</div>

## Reset signal coming from the server ##  {#receive-stream-RESET_STREAM}

<div algorithm="reset signal">
Whenever a [=WebTransport stream=] associated with a {{WebTransportReceiveStream}} |stream| gets a
[=stream-signal/RESET_STREAM=] signal from the server, run these steps:

1. Let |transport| be |stream|.{{WebTransportReceiveStream/[[Transport]]}}.
1. Let |code| be the application protocol error code attached to the RESET_STREAM frame. [[!QUIC]]

   Note: Valid values of |code| are from 0 to 4294967295 inclusive. If the [=underlying connection=] is
   using HTTP/3, the code will be encoded to a number in [0x52e4a40fa8db, 0x52e5ac983162] as decribed in
   [[!WEB-TRANSPORT-HTTP3]].

1. [=Queue a network task=] with |transport| to run these steps:
  1. If |transport|.{{[[State]]}} is `"closed"` or `"failed"`, abort these steps.
  1. [=set/Remove=] |stream| from |transport|.{{[[ReceiveStreams]]}}.
  1. Let |error| be a newly [=DOMException/created=] {{WebTransportError}} whose
     {{WebTransportErrorOptions/source}} is `"stream"` and
     {{WebTransportErrorOptions/streamErrorCode}} is |code|.
  1. [=ReadableStream/Error=] |stream| with |error|.

</div>

## `WebTransportReceiveStreamStats` Dictionary ##  {#receive-stream-stats}

The <dfn dictionary>WebTransportReceiveStreamStats</dfn> dictionary includes
information on stats specific to one {{WebTransportReceiveStream}}.

<pre class="idl">
dictionary WebTransportReceiveStreamStats {
  unsigned long long bytesReceived;
  unsigned long long bytesRead;
};
</pre>

The dictionary SHALL have the following attributes:

: <dfn for="WebTransportReceiveStreamStats" dict-member>bytesReceived</dfn>
:: An indicator of progress on how many of the server application's bytes
   intended for this {{WebTransportReceiveStream}} have been received so far.
   Only sequential bytes up to, but not including, the first missing byte, are
   counted. This number can only increase.

   Note: this is progress of app data received on a single stream only, and does
   not include any network overhead.
: <dfn for="WebTransportReceiveStreamStats" dict-member>bytesRead</dfn>
:: The total number of bytes the application has successfully read from this
   {{WebTransportReceiveStream}}. This number can only increase, and is always
   less than or equal to {{WebTransportReceiveStreamStats/bytesReceived}}.

# Interface `WebTransportBidirectionalStream` #  {#bidirectional-stream}

<pre class="idl">
[Exposed=(Window,Worker), SecureContext]
interface WebTransportBidirectionalStream {
  readonly attribute WebTransportReceiveStream readable;
  readonly attribute WebTransportSendStream writable;
};
</pre>

## Internal slots ## {#bidirectional-stream-internal-slots}

A {{WebTransportBidirectionalStream}} has the following internal slots.

<table class="data" dfn-for="WebTransportBidirectionalStream" dfn-type="attribute">
 <thead>
  <tr>
   <th>Internal Slot
   <th>Description (<em>non-normative</em>)
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>`[[Readable]]`</dfn>
   <td class="non-normative">A {{WebTransportReceiveStream}}.
  </tr>
  <tr>
   <td><dfn>`[[Writable]]`</dfn>
   <td class="non-normative">A {{WebTransportSendStream}}.
  </tr>
  <tr>
   <td><dfn>`[[Transport]]`</dfn>
   <td class="non-normative">The {{WebTransport}} object owning this
   {{WebTransportBidirectionalStream}}.
  </tr>
</table>

## Attributes ##  {#bidirectional-stream-attributes}

: <dfn for="WebTransportBidirectionalStream" attribute>readable</dfn>
:: The getter steps are to return [=this=]'s {{WebTransportBidirectionalStream/[[Readable]]}}.

: <dfn for="WebTransportBidirectionalStream" attribute>writable</dfn>
:: The getter steps are to return [=this=]'s {{WebTransportBidirectionalStream/[[Writable]]}}.

## Procedures ## {#bidirectional-stream-procedures}

<div algorithm="create a BidirectionalStream">
To <dfn for=BidirectionalStream>create</dfn> a {{WebTransportBidirectionalStream}} with a
[=bidirectional=] [=WebTransport stream=] |internalStream|, a {{WebTransport}}
object |transport|, and a |sendOrder|, run these steps.

1. Let |readable| be the result of [=WebTransportReceiveStream/creating=] a {{WebTransportReceiveStream}} with
   |internalStream| and |transport|.
1. Let |writable| be the result of [=WebTransportSendStream/creating=] a {{WebTransportSendStream}} with
   |internalStream|, |transport|, and |sendOrder|.
1. Let |stream| be a [=new=] {{WebTransportBidirectionalStream}}, with:
    : {{WebTransportBidirectionalStream/[[Readable]]}}
    :: |readable|
    : {{WebTransportBidirectionalStream/[[Writable]]}}
    :: |writable|
    : {{WebTransportBidirectionalStream/[[Transport]]}}
    :: |transport|
1. Return |stream|.

</div>

# `WebTransportWriter` Interface #  {#web-transport-writer-interface}

{{WebTransportWriter}} is a subclass of {{WritableStreamDefaultWriter}} that
adds one method.

A {{WebTransportWriter}} is always created by the
[=WebTransportWriter/create=] procedure.

<pre class="idl">
[Exposed=*, SecureContext]
interface WebTransportWriter : WritableStreamDefaultWriter {
  Promise&lt;undefined&gt; atomicWrite(optional any chunk);
};
</pre>

## Methods ##  {#web-transport-writer-methods}

: <dfn for="WebTransportWriter" method>atomicWrite(chunk)</dfn>
:: The {{atomicWrite}} method will reject if the |chunk| given to it
   could not be sent in its entirety within the [=flow control=] window that
   is current at the time of sending. This behavior is designed to satisfy niche
   transactional applications sensitive to [=flow control=] deadlocks ([[RFC9308]]
   [Section 4.4](https://datatracker.ietf.org/doc/html/rfc9308#section-4.4)).

   Note: {{atomicWrite}} can still reject after sending some data. Though it
   provides atomicity with respect to flow control, other errors may occur. 
   {{atomicWrite}} does not prevent data from being split between packets
   or being interleaved with other data. Only the sender learns if
   {{atomicWrite}} fails due to lack of available flow control credit.

   Note: Atomic writes can still block if queued behind non-atomic writes. If
   the atomic write is rejected, everything queued behind it at that moment
   will be rejected as well. Any non-atomic writes rejected in this way will
   [=WritableStream/error=] the stream. Applications are therefore encouraged to
   always await atomic writes.

   When {{atomicWrite}} is called, the user agent MUST run the following steps:
   1. Let |p| be the result of {{WritableStreamDefaultWriter/write(chunk)}}
       on {{WritableStreamDefaultWriter}} with |chunk|.
   1. [=set/Append=] |p| to |stream|.{{WebTransportSendStream/[[AtomicWriteRequests]]}}.
   1. Return the result of [=reacting=] to |p| with the following steps:
      1. If |stream|.{{WebTransportSendStream/[[AtomicWriteRequests]]}} [=list/contains=] |p|,
         [=list/remove=] |p|.
      1. If |p| was rejected with reason |r|, then return [=a promise rejected with=] |r|.
      1. Return undefined.

## Procedures ##  {#web-transport-writer-procedures}

<div algorithm="create a writer">

To <dfn export for="WebTransportWriter" lt="create|creating">create</dfn> a
{{WebTransportWriter}}, with a {{WebTransportSendStream}} |stream|, run these
steps:
1. Let |writer| be a [=new=] {{WebTransportWriter}}.
1. Run the [new WritableStreamDefaultWriter(stream)](https://streams.spec.whatwg.org/#default-writer-constructor)
   constructor steps passing |writer| as this, and |stream| as the constructor argument.
1. Return |writer|.

</div>

# `WebTransportError` Interface #  {#web-transport-error-interface}

<dfn interface>WebTransportError</dfn> is a subclass of {{DOMException}} that represents

 - An error coming from the server or the network, or
 - A reason for a client-initiated abort operation.

<pre class="idl">
[Exposed=(Window,Worker), Serializable, SecureContext]
interface WebTransportError : DOMException {
  constructor(optional DOMString message = "", optional WebTransportErrorOptions options = {});

  readonly attribute WebTransportErrorSource source;
  readonly attribute unsigned long? streamErrorCode;
};

dictionary WebTransportErrorOptions {
  WebTransportErrorSource source = "stream";
  [Clamp] unsigned long? streamErrorCode = null;
};

enum WebTransportErrorSource {
  "stream",
  "session",
};
</pre>

## Internal slots  ## {#web-transport-error-internal-slots}

A {{WebTransportError}} has the following internal slots.

<table class="data" dfn-for="WebTransportError" dfn-type="attribute">
 <thead>
  <tr>
   <th>Internal Slot
   <th>Description (<em>non-normative</em>)
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><dfn>`[[Source]]`</dfn>
   <td class="non-normative">A {{WebTransportErrorSource}} indicating the source of this error.
  </tr>
  <tr>
   <td><dfn>`[[StreamErrorCode]]`</dfn>
   <td class="non-normative">The application protocol error code for this error, or null.
  </tr>
 </tbody>
</table>

## Constructor ##  {#web-transport-error-constructor1}

<div algorithm>

The <dfn constructor for="WebTransportError"
lt="WebTransportError(message, options)">new WebTransportError(message, options)</dfn>
constructor steps are:

1. Set |this|'s [=DOMException/name=] to `"WebTransportError"`.
1. Set |this|'s [=DOMException/message=] to |message|.
1. Set |this|'s internal slots as follows:
    : {{WebTransportError/[[Source]]}}
    :: |options|.{{WebTransportErrorOptions/source}}
    : {{WebTransportError/[[StreamErrorCode]]}}
    :: |options|.{{WebTransportErrorOptions/streamErrorCode}}

   Note: This name does not have a mapping to a legacy code, so [=this=]'s {{DOMException/code}}
   is 0.

</div>

## Attributes ## {#web-transport-error-attributes}

: <dfn for="WebTransportError" attribute>source</dfn>
:: The getter steps are to return [=this=]'s {{WebTransportError/[[Source]]}}.
: <dfn for="WebTransportError" attribute>streamErrorCode</dfn>
:: The getter steps are to return [=this=]'s {{WebTransportError/[[StreamErrorCode]]}}.

## Serialization ## {#web-transport-error-serialization}

{{WebTransportError}} objects are [=serializable objects=].
Their [=serialization steps=], given |value| and |serialized|, are:

1. Run the {{DOMException}} [=serialization steps=] given |value| and |serialized|.
1. Set |serialized|.`[[Source]]` to |value|.{{WebTransportError/[[Source]]}}.
1. Set |serialized|.`[[StreamErrorCode]]` to |value|.{{WebTransportError/[[StreamErrorCode]]}}.

Their [=deserialization steps=], given |serialized| and |value|, are:

1. Run the {{DOMException}} [=deserialization steps=] given |serialized| and |value|.
1. Set |value|.{{WebTransportError/[[Source]]}} to |serialized|.`[[Source]]`.
1. Set |value|.{{WebTransportError/[[StreamErrorCode]]}} |serialized|.`[[StreamErrorCode]]`.

# Protocol Mappings # {#protocol-mapping}

*This section is non-normative.*

This section describes the underlying protocol behavior of methods defined
in this specification, utilizing [[!WEB-TRANSPORT-OVERVIEW]]. Cause and effect may
not be immediate due to buffering.

  <table class="data">
    <colgroup class="header"><col></colgroup>
    <colgroup><col></colgroup>
    <thead>
      <tr>
        <th>WebTransport Protocol Action</th>
        <th>API Effect</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>received [=session-signal/DRAIN_WEBTRANSPORT_SESSION=]</td>
        <td>await wt.{{WebTransport/draining}}</td>
      </tr>
    </tbody>
  </table>

If the [=underlying connection=] is using HTTP/3, the following protocol behaviors
from [[!WEB-TRANSPORT-HTTP3]] apply.

The application {{WebTransportError/streamErrorCode}} in the {{WebTransportError}} error is
converted to an httpErrorCode, and vice versa, as specified in [[!WEB-TRANSPORT-HTTP3]]
[Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.3).

  <table class="data">
    <colgroup class="header"><col></colgroup>
    <colgroup><col></colgroup>
    <thead>
      <tr>
        <th>API Method</th>
        <th>QUIC Protocol Action</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.{{WritableStream/abort}}(error)</td>
        <td>[=stream/Reset|sends RESET_STREAM=] with httpErrorCode</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.{{WritableStream/close}}()</td>
        <td>[=stream/Send|sends=] STREAM with FIN bit set</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.getWriter().{{WritableStreamDefaultWriter/write(chunk)}}()</td>
        <td>[=stream/Send|sends=] STREAM</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.getWriter().{{WritableStreamDefaultWriter/close}}()</td>
        <td>[=stream/Send|sends=] STREAM with FIN bit set</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.getWriter().{{WritableStreamDefaultWriter/abort}}(error)</td>
        <td>[=stream/Reset|sends RESET_STREAM=] with httpErrorCode</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/readable}}.{{ReadableStream/cancel}}(error)</td>
        <td>[=send STOP_SENDING|sends STOP_SENDING=] with httpErrorCode</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/readable}}.getReader().{{ReadableStreamGenericReader/cancel}}(error)</td>
        <td>[=send STOP_SENDING|sends STOP_SENDING=] with httpErrorCode</td>
      </tr>
      <tr>
        <td>wt.{{WebTransport/close}}(closeInfo)</td>
        <td>[=session/terminate|terminates=] session with closeInfo<br>
      </tr>
    </tbody>
  </table>

  <table class="data">
    <colgroup class="header"><col></colgroup>
    <colgroup><col></colgroup>
    <thead>
      <tr>
        <th>QUIC Protocol Action</th>
        <th>API Effect</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>received [=stream-signal/STOP_SENDING=] with httpErrorCode</td>
        <td>[=WritableStream/Error|errors=] {{WebTransportBidirectionalStream/writable}}
        with {{WebTransportError/streamErrorCode}}</td>
      </tr>
      <tr>
        <td>[=stream/Receive|received=] STREAM</td>
        <td>(await
          {{WebTransportBidirectionalStream/readable}}.getReader().{{ReadableStreamDefaultReader/read}}()).value</td>
      </tr>
      <tr>
        <td>[=stream/Receive|received=] STREAM with FIN bit set</td>
        <td>(await
          {{WebTransportBidirectionalStream/readable}}.getReader().{{ReadableStreamDefaultReader/read}}()).done</td>
      </tr>
      <tr>
        <td>received [=stream-signal/RESET_STREAM=] with httpErrorCode</td>
        <td>[=ReadableStream/Error|errors=] {{WebTransportBidirectionalStream/readable}}
        with {{WebTransportError/streamErrorCode}}</td>
      </tr>
      <tr>
        <td>Session cleanly [=session/terminated|terminated=] with closeInfo<br>
        <td>(await wt.{{WebTransport/closed}}).closeInfo, and
         [=ReadableStream/error|errors=] open streams</td>
      </tr>
      <tr>
        <td>Network error<br>
        <td>(await wt.{{WebTransport/closed}}) rejects, and
          [=ReadableStream/error|errors=] open streams</td>
      </tr>
    </tbody>
  </table>
  
Note: As discussed in [[QUIC]] Section 3.2, receipt of a RESET_STREAM
frame is not always indicated to the application. Receipt of the
RESET_STREAM can be signaled immediately, interrupting delivery of
stream data with any data not consumed being discarded. However,
immediate signaling is not required. Also, if stream data
is completely received but has not yet been read by the
application, the RESET_STREAM signal can be suppressed.

  <table class="data">
    <colgroup class="header"><col></colgroup>
    <colgroup><col></colgroup>
    <thead>
      <tr>
        <th>HTTP/3 Protocol Action</th>
        <th>API Effect</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>received [=session-signal/GOAWAY=]</td>
        <td>await wt.{{WebTransport/draining}}</td>
      </tr>
    </tbody>
  </table>

If the [=underlying connection=] is using HTTP/2, the following protocol behaviors
from [[!WEB-TRANSPORT-HTTP2]] apply. Note that, unlike for HTTP/3, the stream error
code does not need to be converted to an HTTP error code, and vice versa.


  <table class="data">
    <colgroup class="header"><col></colgroup>
    <colgroup><col></colgroup>
    <thead>
      <tr>
        <th>API Method</th>
        <th>HTTP/2 Protocol Action</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.{{WritableStream/abort}}(error)</td>
        <td>[=stream/Reset|sends WT_RESET_STREAM=] with error</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.{{WritableStream/close}}()</td>
        <td>[=stream/Send|sends=] WT_STREAM with FIN bit set</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.getWriter().{{WritableStreamDefaultWriter/write}}()</td>
        <td>[=stream/Send|sends=] WT_STREAM</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.getWriter().{{WritableStreamDefaultWriter/close}}()</td>
        <td>[=stream/Send|sends=] WT_STREAM with FIN bit set</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/writable}}.getWriter().{{WritableStreamDefaultWriter/abort}}(error)</td>
        <td>[=stream/Reset|sends WT_RESET_STREAM=] with error</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/readable}}.{{ReadableStream/cancel}}(error)</td>
        <td>[=send STOP_SENDING|sends WT_STOP_SENDING=] with error</td>
      </tr>
      <tr>
        <td>{{WebTransportBidirectionalStream/readable}}.getReader().{{ReadableStreamGenericReader/cancel}}(error)</td>
        <td>[=send STOP_SENDING|sends WT_STOP_SENDING=] with error</td>
      </tr>
      <tr>
        <td>wt.{{WebTransport/close}}(closeInfo)</td>
        <td>[=session/terminate|terminates=] session with closeInfo<br>
      </tr>
    </tbody>
  </table>

  <table class="data">
    <colgroup class="header"><col></colgroup>
    <colgroup><col></colgroup>
    <thead>
      <tr>
        <th>HTTP/2 Protocol Action</th>
        <th>API Effect</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>received [=stream-signal/STOP_SENDING|WT_STOP_SENDING=] with error</td>
        <td>[=WritableStream/Error|errors=] {{WebTransportBidirectionalStream/writable}}
        with {{WebTransportError/streamErrorCode}}</td>
      </tr>
      <tr>
        <td>[=stream/Receive|received=] WT_STREAM</td>
        <td>(await
          {{WebTransportBidirectionalStream/readable}}.getReader().{{ReadableStreamDefaultReader/read}}()).value</td>
      </tr>
      <tr>
        <td>[=stream/Receive|received=] WT_STREAM with FIN bit set</td>
        <td>(await
          {{WebTransportBidirectionalStream/readable}}.getReader().{{ReadableStreamDefaultReader/read}}()).done</td>
      </tr>
      <tr>
        <td>received [=stream-signal/RESET_STREAM|WT_RESET_STREAM=] with error</td>
        <td>[=ReadableStream/Error|errors=] {{WebTransportBidirectionalStream/readable}}
        with {{WebTransportError/streamErrorCode}}</td>
      </tr>
      <tr>
        <td>Session cleanly [=session/terminated|terminated=] with closeInfo<br>
        <td>(await wt.{{WebTransport/closed}}).closeInfo, and
         [=ReadableStream/error|errors=] open streams</td>
      </tr>
      <tr>
        <td>Network error<br>
        <td>(await wt.{{WebTransport/closed}}) rejects, and
          [=ReadableStream/error|errors=] open streams</td>
      </tr>
      <tr>
        <td>received [=session-signal/GOAWAY=]</td>
        <td>await wt.{{WebTransport/draining}}</td>
      </tr>
    </tbody>
  </table>

# Privacy and Security Considerations #  {#privacy-security}

This section is non-normative; it specifies no new behaviour, but instead
summarizes information already present in other parts of the specification.

## Confidentiality of Communications ##  {#confidentiality}

The fact that communication is taking place cannot be hidden from adversaries
that can observe the network, so this has to be regarded as public information.

All of the transport protocols described in this document use either TLS
[[RFC8446]] or a semantically equivalent protocol, thus providing all of the
security properties of TLS, including confidentiality and integrity of the
traffic. WebTransport over HTTP uses the same certificate verification
mechanism as outbound HTTP requests, thus relying on the same public key
infrastructure for authentication of the remote server. In WebTransport,
certificate verification errors are fatal; no interstitial allowing bypassing
certificate validation is available.

## State Persistence ##  {#state-persistence}

WebTransport does not by itself create any new unique identifiers or new
ways to persistently store state, nor does it automatically expose any of
the existing persistent state to the server. For instance, neither
[[WEB-TRANSPORT-HTTP3]] nor [[WEB-TRANSPORT-HTTP2]] send cookies or support
HTTP authentication or caching invalidation mechanisms.  Since they do use
TLS, they inherit TLS persistent state such as TLS session tickets,
which while not visible to passive network observers, could be used by
the server to correlate different connections from the same client.

## Protocol Security ##  {#protocol-security}

WebTransport imposes a set of requirements as described in
[[!WEB-TRANSPORT-OVERVIEW]], including:

1. Ensuring that the remote server is aware that the WebTransport protocol is in
   use and confirming that the remote server is willing to use the WebTransport
   protocol. [[!WEB-TRANSPORT-HTTP3]] uses a combination of ALPN [[RFC7301]], an
   HTTP/3 setting, and a `:protocol` pseudo-header to identify the WebTransport
   protocol. [[!WEB-TRANSPORT-HTTP2]] uses a combination of ALPN, an HTTP/2 setting,
   and a `:protocol` pseudo-header to identify the WebTransport protocol.
1. Allowing the server to filter connections based on the origin of the resource
   originating the transport session.  The <a http-header><code>Origin</code></a> header
   field on the session establishment request carries this information.

Protocol security related considerations are described in the
*Security Considerations* sections of [[!WEB-TRANSPORT-HTTP3]] and
[[!WEB-TRANSPORT-HTTP2]].

Networking APIs can be commonly used to scan the local network for available
hosts, and thus be used for fingerprinting and other forms of attacks.
WebTransport follows the [WebSocket
approach](https://websockets.spec.whatwg.org/#feedback-from-the-protocol)
to this problem: the specific connection error is not returned until an
endpoint is verified to be a WebTransport endpoint; thus, the Web application
cannot distinguish between a non-existing endpoint and the endpoint that is not
willing to accept connections from the Web.

## Authentication using Certificate Hashes {#certificate-hashes}

Normally, a user agent authenticates a TLS connection between itself and a
remote endpoint by verifying the validity of the TLS server certificate
provided against the server name in the URL [[!RFC9525]].  This is accomplished
by chaining server certificates to one of the trust anchors maintained by the
user agent; the trust anchors in question are responsible for authenticating
the server names in the certificates.  We will refer to this system as Web PKI.

This API provides web applications with a capability to connect to a remote
network endpoint authenticated by a specific server certificate, rather than
its server name.  This mechanism enables connections to endpoints for which
getting long-term certificates can be challenging, including hosts that are
ephemeral in nature (e.g. short-lived virtual machines), or that are not
publicly routable.  Since this mechanism substitutes Web PKI-based
authentication for an individual connection, we need to compare the security
properties of both.

A remote server will be able to successfully perform a TLS handshake only if it
posesses the private key corresponding to the public key of the certificate
specified.  The API identifies the certificates using their hashes.  That is
only secure as long as the cryptographic hash function used has second-preimage
resistance.  The only function defined in this document is SHA-256; the API
provides a way to introduce new hash functions through allowing multiple
algorithm-hash pairs to be specified.

It is important to note that Web PKI provides additional security
mechanisms in addition to simply establishing a chain of trust for a server
name.  One of them is handling certificate revocation.  In cases where the
certificate used is ephemeral, such a mechanism is not necessary.  In other
cases, the Web application has to consider the mechanism by which the
certificate hashes are provisioned; for instance, if the hash is provided as a
cached HTTP resource, the cache needs to be invalidated if the corresponding
certificate has been rotated due to compromise.  Another security feature
provided by the Web PKI are safeguards against certain issues with key
generation, such as rejecting certificates with known weak keys; while
this specification does not provide any specific guidance, browsers MAY reject
those as a part of implementation-defined behavior.

Web PKI enforces an expiry period requirement on the certificates.  This
requirement limits the scope of potential key compromise; it also forces server
operators to design systems that support and actively perform key rotation.
For this reason, WebTransport imposes a similar expiry requirement; as the
certificates are expected to be ephemeral or short-lived, the expiry period is
limited to two weeks.  The two weeks limit is a balance between setting the
expiry limit as low as possible to minimize consequences of a key compromise,
and maintaining it sufficiently high to accomodate for clock skew across
devices, and to lower the costs of synchronizing certificates between the
client and the server side.

The WebTransport API lets the application specify multiple certificate
hashes at once, allowing the client to accept multiple certificates for a
period in which a new certificate is being rolled out.

Unlike a similar mechanism in [=WebRTC=], the server certificate hash API in
WebTransport does not provide any means of authenticating the client; the fact
that the client knows what the server certificate is or how to contact it is
not sufficient.  The application has to establish the identity of the client
in-band if necessary.

## Fingerprinting and Tracking ## {#fingerprinting}

This API provides sites with the ability to generate network activity and
closely observe the effect of this activity.  The information obtained in this
way might be <a
href="https://infra.spec.whatwg.org/#tracking-vector">identifying</a>.

It is important to recognize that very similar networking capabilities are
provided by other web platform APIs (such as [=fetch=] and [[webrtc]]).  The net
adverse effect on privacy due to adding WebTransport is therefore minimal.  The
considerations in this section applies equally to other networking capabilities.

Measuring network characteristics requires that the network be used and that the
effect of that usage be measured, both of which are enabled by this API.
WebTransport provides sites with an ability to generate network activity toward
a server of their choice and observe the effects.  Observations of both the
stable properties of a network path and dynamic effect of network usage are
possible.

Information about the network is available to the server either directly through
its own networking stack, indirectly through the rate at which data is consumed
or transmitted by the client, or as part of the statistics provided by the API
(see [[#web-transport-connection-stats]]).  Consequently, restrictions on information in
user agents is not the only mechanism that might be needed to manage these
privacy risks.

### Static Observations ### {#fp-static}

A site can observe available network capacity or round trip time (RTT) between a
user agent and a chosen server.  This information can be identifying when
combined with other tracking vectors.  RTT can also reveal something about the
physical location of a user agent, especially if multiple measurements can be
made from multiple vantage points.

Though networking is shared, network use is often sporadic, which means that
sites are often able to observe the capacity and round trip times of an
uncontested or lightly loaded network path.  These properties are stable for
many people as their network location does not change and the position of
network bottlenecks--which determine available capacity--can be close to a user
agent.

### Shared Networking ### {#fp-shared}

Contested links present sites with opportunities to enable
[[privacy-principles#dfn-cross-site-recognition|cross-site recognition]],
which might be used to perform unsanctioned tracking [[UNSANCTIONED-TRACKING]].
Network capacity is a finite shared resource, so a user agent that concurrently
accesses different sites might reveal a connection between the identity
presented to each site.

The use of networking capabilities on one site reduces the capacity available to
other sites, which can be observed using networking APIs.  Network usage and
metrics can change dynamically, so any change can be observed in real time.
This might allow sites to increase confidence that activity on different sites
originates from the same user.

A user agent could limit or degrade access to feedback mechanisms such as
statistics ([[#web-transport-connection-stats]]) for sites that are inactive or do not have
focus ([[html/interaction#focus]]).  As noted, this does not prevent a server
from making observations about changes in the network.

### Pooled Sessions ### {#fp-pooled}

Similar to shared networking scenarios, when sessions are pooled on a single
connection, information from one session is affected by the activity of another
session.  One session could infer information about the activity of another
session, such as the rate at which another application is sending data.

The use of a shared connection already allows the server to correlate sessions.
Use of a [[fetch#network-partition-keys|network partition key]] disables pooling
where use of a shared session might enable unwanted cross-site recognition.

# Examples #  {#examples}

## Sending a buffer of datagrams ##  {#example-datagrams}

*This section is non-normative.*

Sending a buffer of datagrams can be achieved by using the
{{WebTransport/datagrams}}' {{WebTransportDatagramDuplexStream/writable}} attribute. In the
following example datagrams are only sent if the transport is ready to send.

<pre class="example" highlight="js">
async function sendDatagrams(url, datagrams) {
  const wt = new WebTransport(url);
  const writer = wt.datagrams.writable.getWriter();
  for (const bytes of datagrams) {
    await writer.ready;
    writer.write(bytes).catch(() => {});
  }
}
</pre>

## Sending datagrams at a fixed rate ##  {#example-fixed-rate}

*This section is non-normative.*

Sending datagrams at a fixed rate regardless if the transport is ready to send
can be achieved by simply using {{WebTransport/datagrams}}'
{{WebTransportDatagramDuplexStream/writable}} and not using the `ready` attribute. More complex
scenarios can utilize the `ready` attribute.

<pre class="example" highlight="js">
// Sends datagrams every 100 ms.
async function sendFixedRate(url, createDatagram, ms = 100) {
  const wt = new WebTransport(url);
  await wt.ready;
  const writer = wt.datagrams.writable.getWriter();
  const bytes = createDatagram();
  setInterval(() => writer.write(bytes).catch(() => {}), ms);
}
</pre>

## Receiving datagrams ##  {#example-receiving-datagrams}

*This section is non-normative.*

Datagrams can be received by reading from the
transport.{{WebTransport/datagrams}}.{{WebTransportDatagramDuplexStream/readable}}
attribute. Null values may indicate that packets are not being processed quickly
enough.

<pre class="example" highlight="js">
async function receiveDatagrams(url) {
  const wt = new WebTransport(url);
  for await (const datagram of wt.datagrams.readable) {
    // Process the datagram
  }
}
</pre>

## Receiving datagrams with a BYOB reader ## {#example-datagrams-byob}

*This section is non-normative.*

As {{WebTransport/datagrams}} are [=readable byte streams=], you can acquire a
[=BYOB reader=] for them, which allows more precise control over buffer allocation
in order to avoid copies. This example reads the datagram into a 64kB memory buffer.

<pre class="example" highlight="js">
const wt = new WebTransport(url);

for await (const datagram of wt.datagrams.readable) {
  const reader = datagram.getReader({ mode: "byob" });
  
  let array_buffer = new ArrayBuffer(65536);
  const buffer = await readInto(array_buffer);
}

async function readInto(buffer) {
  let offset = 0;

  while (offset < buffer.byteLength) {
    const {value: view, done} = await reader.read(
        new Uint8Array(buffer, offset, buffer.byteLength - offset));
    buffer = view.buffer;
    if (done) {
      break;
    }
    offset += view.byteLength;
  }

  return buffer;
}
</pre>

## Sending a stream ##  {#example-sending-stream}

*This section is non-normative.*

Sending data as a one-way stream can be achieved by using the
{{WebTransport/createUnidirectionalStream}} function and the resulting stream's writer.

<pre class="example" highlight="js">
async function sendData(url, ...data) {
  const wt = new WebTransport(url);
  const writable = await wt.createUnidirectionalStream();
  const writer = writable.getWriter();
  for (const bytes of data) {
    await writer.ready;
    writer.write(bytes).catch(() => {});
  }
  await writer.close();
}
</pre>
<div class="note">
  <p>The streams spec
    [discourages](https://streams.spec.whatwg.org/#example-manual-write-dont-await)
    awaiting the promise from write().</p>
</div>

Encoding can also be done through pipes from a {{ReadableStream}}, for example using
{{TextEncoderStream}}.

<pre class="example" highlight="js">
async function sendText(url, readableStreamOfTextData) {
  const wt = new WebTransport(url);
  const writable = await wt.createUnidirectionalStream();
  await readableStreamOfTextData
    .pipeThrough(new TextEncoderStream("utf-8"))
    .pipeTo(writable);
}
</pre>

## Receiving incoming streams ##  {#example-receiving-incoming-streams}

*This section is non-normative.*

Reading incoming streams can be achieved by iterating over the
{{WebTransport/incomingUnidirectionalStreams}} attribute,
and then consuming each {{WebTransportReceiveStream}} by iterating over its chunks.

<pre class="example" highlight="js">
async function receiveData(url, processTheData) {
  const wt = new WebTransport(url);
  for await (const readable of wt.incomingUnidirectionalStreams) {
    // consume streams individually using IFFEs, reporting per-stream errors
    ((async () => {
      try {
        for await (const bytes of readable) {
          processTheData(bytes);
        }
      } catch (e) {
        console.error(e);
      }
    })());
  }
}
</pre>

Decoding can also be done through pipes to new WritableStreams, for example using
{{TextDecoderStream}}. This example assumes text output should not be
interleaved, and therefore only reads one stream at a time.

<pre class="example" highlight="js">
async function receiveText(url, createWritableStreamForTextData) {
  const wt = new WebTransport(url);
  for await (const readable of wt.incomingUnidirectionalStreams) {
    // consume sequentially to not interleave output, reporting per-stream errors
    try {
      await readable
       .pipeThrough(new TextDecoderStream("utf-8"))
       .pipeTo(createWritableStreamForTextData());
    } catch (e) {
      console.error(e);
    }
  }
}
</pre>

## Receiving a stream with a BYOB reader ## {#example-stream-byob}

*This section is non-normative.*

As {{WebTransportReceiveStream}}s are [=readable byte streams=], you can acquire a
[=BYOB reader=] for them, which allows more precise control over buffer allocation
in order to avoid copies. This example reads the first 1024 bytes from a
{{WebTransportReceiveStream}} into a single memory buffer.

<pre class="example" highlight="js">
const wt = new WebTransport(url);

const reader = wt.incomingUnidirectionalStreams.getReader();
const { value: recv_stream, done } = await reader.read();
const byob_reader = recv_stream.getReader({ mode: "byob" });

let array_buffer = new ArrayBuffer(1024);
const buffer = await readInto(array_buffer);

async function readInto(buffer) {
  let offset = 0;

  while (offset < buffer.byteLength) {
    const {value: view, done} = await reader.read(
        new Uint8Array(buffer, offset, buffer.byteLength - offset));
    buffer = view.buffer;
    if (done) {
      break;
    }
    offset += view.byteLength;
  }

  return buffer;
}
</pre>

## Sending a transactional chunk on a stream ##  {#example-transactional-stream}

*This section is non-normative.*

Sending a transactional piece of data on a unidirectional stream, only if it can be done
entirely without blocking on [=flow control=], can be achieved by using the
{{WebTransportSendStream/getWriter}} function and the resulting writer.

<pre class="example" highlight="js">
async function sendTransactionalData(wt, bytes) {
  const writable = await wt.createUnidirectionalStream();
  const writer = writable.getWriter();
  await writer.ready;
  try {
    await writer.atomicWrite(bytes);
  } catch (e) {
    if (e.name != "AbortError") throw e;
    // rejected to avoid blocking on flow control
    // The writable remains un-errored provided no non-atomic writes are pending
  } finally {
    writer.releaseLock();
  }
}
</pre>

## Using a server certificate hash ## {#example-server-certificate-hash}

*This section is non-normative.*

A WebTransport session can override the default trust evaluation performed by
the client with a check against the hash of the certificate provided to the
server. In the example below, `hashValue` is a {{BufferSource}} containing the
SHA-256 hash of a server certificate that the [=underlying connection=] should
consider to be valid.

<pre class="example" highlight="js">
const wt = new WebTransport(url, {
  serverCertificateHashes: [
    {
      algorithm: "sha-256",
      value: hashValue,
    }
  ]
});
await wt.ready;
</pre>

## Complete example ##  {#example-complete}

*This section is non-normative.*

This example illustrates use of the closed and ready promises, opening
of uni-directional and bi-directional streams by either the client or
the server, and sending and receiving datagrams.

<pre class="example" highlight="js">
// Adds an entry to the event log on the page, optionally applying a specified
// CSS class.

let wt, streamNumber, datagramWriter;

connect.onclick = async () => {
  try {
    const url = document.getElementById('url').value;

    wt = new WebTransport(url);
    addToEventLog('Initiating connection...');
    await wt.ready;
    addToEventLog(\`${(wt.reliability == "reliable-only")? "TCP" : "UDP"} \` +
                  \`connection ready.\`);
    wt.closed
      .then(() => addToEventLog('Connection closed normally.'))
      .catch(() => addToEventLog('Connection closed abruptly.', 'error'));

    streamNumber = 1;
    datagramWriter = wt.datagrams.writable.getWriter();

    readDatagrams();
    acceptUnidirectionalStreams();
    document.forms.sending.elements.send.disabled = false;
    document.getElementById('connect').disabled = true;
  } catch (e) {
    addToEventLog(&#96;Connection failed. ${e}&#96;, 'error');
  }
}

sendData.onclick = async () => {
  const form = document.forms.sending.elements;
  const data = sending.data.value;
  const bytes = new TextEncoder('utf-8').encode(data);
  try {
    switch (form.sendtype.value) {
      case 'datagram': {
        await datagramWriter.ready;
        datagramWriter.write(bytes).catch(() => {});
        addToEventLog(&#96;Sent datagram: ${data}&#96;);
        break;
      }
      case 'unidi': {
        const writable = await wt.createUnidirectionalStream();
        const writer = writable.getWriter();
        writer.write(bytes).catch(() => {});
        await writer.close();
        addToEventLog(&#96;Sent a unidirectional stream with data: ${data}&#96;);
        break;
      }
      case 'bidi': {
        const duplexStream = await wt.createBidirectionalStream();
        const n = streamNumber++;
        readFromIncomingStream(duplexStream.readable, n);

        const writer = duplexStream.writable.getWriter();
        writer.write(bytes).catch(() => {});
        await writer.close();
        addToEventLog(&#96;Sent bidirectional stream #${n} with data: ${data}&#96;);
        break;
      }
    }
  } catch (e) {
    addToEventLog(&#96;Error while sending data: ${e}&#96;, 'error');
  }
}

// Reads datagrams into the event log until EOF is reached.
async function readDatagrams() {
  try {
    const decoder = new TextDecoderStream('utf-8');

    for await (const data of wt.datagrams.readable.pipeThrough(decoder)) {
      addToEventLog(&#96;Datagram received: ${data}&#96;);
    }
    addToEventLog('Done reading datagrams!');
  } catch (e) {
    addToEventLog(&#96;Error while reading datagrams: ${e}&#96;, 'error');
  }
}

async function acceptUnidirectionalStreams() {
  try {
    for await (const readable of wt.incomingUnidirectionalStreams) {
      const number = streamNumber++;
      addToEventLog(&#96;New incoming unidirectional stream #${number}&#96;);
      readFromIncomingStream(readable, number);
    }
    addToEventLog('Done accepting unidirectional streams!');
  } catch (e) {
    addToEventLog(&#96;Error while accepting streams ${e}&#96;, 'error');
  }
}

async function readFromIncomingStream(readable, number) {
  try {
    const decoder = new TextDecoderStream('utf-8');
    for await (const data of readable.pipeThrough(decoder)) {
      addToEventLog(&#96;Received data on stream #${number}: ${data}&#96;);
    }
    addToEventLog(&#96;Stream #${number} closed&#96;);
  } catch (e) {
    addToEventLog(&#96;Error while reading from stream #${number}: ${e}&#96;, 'error');
    addToEventLog(&#96;    ${e.message}&#96;);
  }
}

function addToEventLog(text, severity = 'info') {
  const log = document.getElementById('event-log');
  const previous = log.lastElementChild;
  const entry = document.createElement('li');
  entry.innerText = text;
  entry.className = &#96;log-${severity}&#96;;
  log.appendChild(entry);

  // If the previous entry in the log was visible, scroll to the new element.
  if (previous &&
      previous.getBoundingClientRect().top < log.getBoundingClientRect().bottom) {
    entry.scrollIntoView();
  }
}
</pre>

# Acknowledgements #  {#acknowledgements}
The editors wish to thank the Working Group chairs and Team Contact, Jan-Ivar Bruaroey, Will Law
and Yves Lafon, for their support.

The {{WebTransport}} interface is based on the `QuicTransport` interface
initially described in the [W3C ORTC CG](https://www.w3.org/community/ortc/),
and has been adapted for use in this specification.
